Taste of Tech Topics

Acroquest Technology株式会社のエンジニアが書く技術ブログ

RxJavaを使ってCallback Hellから脱出する( Java8 ラムダ編 )

id:KenichiroMurata(@ )です。

f:id:acro-engineer:20131206014754j:plain

皆さん、RxJava 使っていますか?

前回はVert.xのmod-rxvertxを使い、RxJavaによってCallback Hellから脱出する方法を説明しました。本記事は、せっかくなのでJava8のラムダを使ったらさらにどのようになるのか?を試してみた補足記事です。

目次は以下の通りです。

  1. 環境
  2. Java8 ラムダによってCallback Hellはいかに解決されたか
  3. まとめ

1. 環境

本記事で試した環境について説明します。

  • Vert.x 2.1M1
  • mod-rxvertx 1.0.0-beta2
  • RxJava 0.15.1
  • Java 1.8.0-ea(build 1.8.0-ea-b118)
  • IntelliJ IDEA 13 CE

Java8を試すために、IntelliJ IDEA 13 CEを使いました。Java8をインストールし、IDEAを使う場合は以下のように初期化してください。

ken@vertx-test $ ./gradlew test
ken@vertx-test $ ./gradlew copyModJson
ken@vertx-test $ ./gradlew idea

2. Java8 ラムダによってCallback Hellはいかに解決されたか

まずは、通常のVert.xで書いた場合のコードのJava8 ラムダ版は次のようになります。例を分かりやすくするために、JSONメッセージの生成部などは外部メソッド化しました。

J8BusModVerticleTest.testSerialAction

  @Test
  public void testSerialAction() {
    final EventBus eventBus = vertx.eventBus();
    final ConcurrentMap<String, String> map = vertx.sharedData().getMap("muraken720.testexample");

    JsonObject req1 = createAddRequest("@muraken720");

    // リクエスト1
    eventBus.send("muraken720.vertx.mod.testexample", req1,
        (Handler<Message<JsonObject>>) reply -> {
          assertEquals("ok", reply.body().getString("status"));
          assertEquals("@muraken720", map.get("name"));

          JsonObject req2 = createAddRequest("Kenichiro");

          // リクエスト2
          eventBus.send("muraken720.vertx.mod.testexample", req2,
              (Handler<Message<JsonObject>>) reply1 -> {
                assertEquals("ok", reply.body().getString("status"));
                assertEquals("Kenichiro", map.get("name"));

                JsonObject req3 = createAddRequest("Murata");

                // リクエスト3
                eventBus.send("muraken720.vertx.mod.testexample", req3,
                    (Handler<Message<JsonObject>>) reply2 -> {
                      assertEquals("ok", reply.body().getString("status"));
                      assertEquals("Murata", map.get("name"));

                      testComplete();
                    });
              });
        });
  }

ラムダ式により、無名クラスの部分がスッキリしていますね。

それではこれをRxJavaを使ってコードを書くと次のようになります。

RxJ8BusModVerticleTest.testSerialAction

  @Test
  public void testSerialAction() {
    final RxEventBus rxEventBus = new RxEventBus(vertx.eventBus());
    final ConcurrentMap<String, String> map = vertx.sharedData().getMap("muraken720.testexample");

    JsonObject req1 = createAddRequest("@muraken720");

    // リクエスト1
    Observable<RxMessage<JsonObject>> obs1 = rxEventBus.send("muraken720.vertx.mod.testexample", req1);

    Observable<RxMessage<JsonObject>> obs2 = obs1.flatMap(reply -> {
      assertEquals("ok", reply.body().getString("status"));
      assertEquals("@muraken720", map.get("name"));

      JsonObject req2 = createAddRequest("Kenichiro");

      return rxEventBus.send("muraken720.vertx.mod.testexample", req2);
    });

    // リクエスト2
    Observable<RxMessage<JsonObject>> obs3 = obs2.flatMap(reply -> {
      assertEquals("ok", reply.body().getString("status"));
      assertEquals("Kenichiro", map.get("name"));

      JsonObject req3 = createAddRequest("Murata");

      return rxEventBus.send("muraken720.vertx.mod.testexample", req3);
    });

    // リクエスト3
    obs3.subscribe(reply -> {
      assertEquals("ok", reply.body().getString("status"));
      assertEquals("Murata", map.get("name"));

      testComplete();
    });
  }

Java8のラムダ式を使うことにより、RxJavaのAction1、Function1といった無味乾燥なクラス名も登場しなくなり、非常にスッキリしました。コード自体も縦には長くなっていますが、ネストが深くならないことがよく分かると思います。

3 まとめ

いかかでしょうか?Java8のラムダを使うことで、Callback Hellから脱出するだけでなく、その記述方法のシンプルになることがご覧頂けたと思います。Java8の正式リリースは3月?だったと思いますが、そのJava8に向けて、RxJavaもVert.xもさらに進化していくと思います。今後どのようになるのか?を想像しながら、あれこれ試してみると面白いと思います。

See also

Acroquest Technologyでは、キャリア採用を行っています。


  • 日頃勉強している成果を、Hadoop、Storm、NoSQL、HTML5/CSS3/JavaScriptといった最新の技術を使ったプロジェクトで発揮したい。
  • 社会貢献性の高いプロジェクトに提案からリリースまで携わりたい。
  • 書籍・雑誌等の執筆や対外的な勉強会の開催を通した技術の発信や、社内勉強会での技術情報共有により、技術的に成長したい。
  • OSSの開発に携わりたい。

 
少しでも上記に興味を持たれた方は、是非以下のページをご覧ください。
 キャリア採用ページ

日経ソフトウエアでJava8の連載を始めました!

娘に「スーパーマリオ3Dワールド」をねだられ、断るに断れず買ってしまった @ です。
っていうかむしろ自分から積極的に買いました。


3Dマリオは初体験だったのですが、フィールドを縦横無尽に走り抜ける感覚は、これまでの2Dマリオにはない斬新さ。
しゃがもうとしてカーソルキーの下を押したら、マリオが手前に走ってくることに当惑しつつも、3Dという新しい要素を取り入れながら、世界観も、キャラクターも、そして動かしている感覚も全て「マリオ」であることを保っている任天堂には感服しますね。

しかも今回のマリオにはネコマr(以下、3000字略)

先取り! Java SE 8

さて、前置きが長くなりましたが「日経ソフトウエア2014年1月号」より「先取り! Java SE 8」という連載を開始しました。

http://itpro.nikkeibp.co.jp/NSW/
http://itpro.nikkeibp.co.jp/NSW/

Acroquestの社員が、Java8の新機能をこれから数ヶ月にわたって紹介する短期集中連載です。

まだ正式リリースされていないJava8を解説するという野心的な連載なわけですが、特に学生や初学者に伝えたいという想いで書きましたので、ぜひ手に取ってみてください。

なお、Acroquestのサイトから2ページ分のPDFを閲覧できますので、こちらも参考にしてください。
http://www.acroquest.co.jp/company/press/2013/1122p1.html

第一回目は「ラムダ式

第一回目の内容は、Java8のアップデート全般とラムダ式について、私が執筆しました。
既にWeb媒体や勉強会などでは紹介されているラムダ式ですが、これまできちんと学ぶ機会がなかったという方や、Webで学んではみたけど紙媒体でも読んでみたい方に、ぜひ読んでもらいたいです。


ソースコード中に出てくる -> という見慣れない表記に当惑しつつも、ラムダ式という新しい要素を取り入れながら、文法も、クラスライブラリも、そして書いている感覚も全て「Java」であることを保っているJavaのスペックリードには感服しますね。

しかもJava8のラムダには型推r(以下、3000字略)


新しいパラダイムを取り入れながらも、古い感覚を保って進化していくところは、マリオもJavaも変わらないということですね。

・・・おあとがよろしいようで m(_ _)m
 

Acroquest Technologyでは、キャリア採用を行っています。


  • 日頃勉強している成果を、Hadoop、Storm、NoSQL、HTML5/CSS3/JavaScriptといった最新の技術を使ったプロジェクトで発揮したい。
  • 社会貢献性の高いプロジェクトに提案からリリースまで携わりたい。
  • 書籍・雑誌等の執筆や対外的な勉強会の開催を通した技術の発信や、社内勉強会での技術情報共有により、技術的に成長したい。
  • OSSの開発に携わりたい。

 
少しでも上記に興味を持たれた方は、是非以下のページをご覧ください。
 キャリア採用ページ

続・今日から始めるJava8 - JSR-310 Date and Time API

こんにちは id:cero-t です。
前回話題にしたJava8 ですが、もう少し別の機能を試してみましょう。
今回は新しい日時APIDate and Time API(JSR-310) を紹介します。


JDK8 M6時点でのJavadocは、以下のURLで公開されています。
http://cr.openjdk.java.net/~rriggs/threeten/threeten-javadoc-b75/


注意:このエントリーの内容は、JDK8 M6 (b75) の時点のAPIに従っています。
M7以降では一部のAPIが変わっているので注意してください。GA時点でまた記事を見直します。


From Joda Time to Three Ten

Javaの日付クラス(java.util.Date)の扱いは、
皆さんもなかなか苦慮していることだと思います。
というのも、、、

・年月日などを指定したインスタンス生成ができない。
・計算をするために、Calendarクラスを使う必要がある。
・月が0オリジンだから、たとえば8月は「7」と表現する。
・SimpleDateFormatがスレッドセーフでない。
・日付だけ扱いたいのに、勝手に時間まで指定されて困る。あるいはその逆。
・たまにjava.sql.Dateと間違う。


このような問題に対して、もっと使いやすい日時APIとして
Joda TimeというOSSのライブラリが開発されました。
http://joda-time.sourceforge.net/

そしてJava8には、Joda Timeから発想を得た
新しいDate and Time API(JSR-310)が入ることになりました。


ちなみに、OpenJDKに入っているJSR-310の参照実装は
「ThreeTen」というコードネームになっています。

アクロバット系競技に興味のある皆様におかれましては
360はThreeSixty、540はFiveFortyなど聞きなれていることと思いますが、
それと同じということですね。

カッチョイイですね。

日付、時間、日時が別クラスに

JSR-310では 日付・時間・日時を扱うクラス がそれぞれ分かれています。
それが「LocalDate」「LocalTime」「LocalDateTime」の3つです。

具体的なコードで見てみましょう。

// 日付
LocalDate date = LocalDate.now();
System.out.println(date);

// 時間
LocalTime time = LocalTime.now();
System.out.println(time);

// 日時
LocalDateTime dateTime = LocalDateTime.now();
System.out.println(dateTime);

出力結果

2013-02-02
21:42:35.126
2013-02-02T21:42:35.126

ご覧の通り、日付のみ、時間のみ、日付と時間の両方の3つに分かれました。

また、toStringした結果がISO 8601形式で読みやすくなっています。
java.util.Dateの時は、toStringしても「Sat Feb 02 21:43:41 JST 2013」という
ちょっと読みにくい形式でした。


また、それぞれのクラスが持つメソッドも異なっており
LocalDateなら「getYear」「getMonth」「getDayOfMonth」
LocalTimeなら「getHour」「getMinute」「getSecond」「getNano」
LocalDateTimeなら、これら全てが使えるようになっています。

APIレベルでこのような制限が掛かっているので、使い間違いがありませんね。

インスタンスの作り方

これまでDateやCalendarのインスタンスを作る場合、
「現在の日時」か「1900/01/01 00:00:00」でのインスタンスを作るか、
DateFormat#parseを使ってインスタンスを作る必要がありました。

java.util.Dateには、年月日を指定してインスタンスを作るAPIもありましたが
現在ではdeprecated扱いになっています。


いっぽうLocalDateTimeでは、3つの作り方があります。
 1. 現在の日時
 2. 年月日時分秒を指定
 3. 日付文字列(ISO 8601形式)を指定

実際のコードで紹介しましょう。

// 現在の日時
System.out.println(LocalDateTime.now());

// 年月日などを指定。秒未満は省略可
System.out.println(LocalDateTime.of(2012, Month.FEBRUARY, 3, 21, 30, 15));
System.out.println(LocalDateTime.of(2012, 2, 3, 21, 30, 15, 123));

// 文字列を指定。秒未満は省略可
System.out.println(LocalDateTime.parse("2012-02-03T21:30:15.123"));

実行結果

2013-02-03T17:06:57.004
2012-02-03T21:30:15
2012-02-03T21:30:15.000000123
2012-02-03T21:30:15.123

月の指定で「2」を使えるところがいい ですね。
これまでは0オリジンなので、2月なら「1」を指定する必要がありました。

また、SimpleDateFormat的なクラスを使わなくとも
文字列から作れるようになったのも便利ですね。

演算も簡単に

これまで面倒だった日時の演算も、JSR-310では簡単になりました。
Date型は演算ができず、Calendarの時は引数にフィールドを指定する必要がありましたが
LocalDateTimeでは、plusDays、minusHoursといったAPIを利用して演算ができるようになりました。

具体的なコードは、このようになります。

// 2012/02/03 21:30:15
LocalDateTime dateTime = LocalDateTime.of(2012, 2, 3, 21, 30, 15);

// 3日後
System.out.println(dateTime.plusDays(3));

// 100日前
System.out.println(dateTime.minusDays(100));

// 30分前
System.out.println(dateTime.minusSeconds(30));

// 元のインスタンスの値は・・・?
System.out.println(dateTime);

実行結果

2012-02-06T21:30:15
2011-10-26T21:30:15
2012-02-03T21:29:45
2012-02-03T21:30:15

LocalDateTimeクラスは imutable なので演算を行った後でも
最初に作ったdateTimeインスタンスの日時が変化していないところにも注目です。
(ちなみに スレッドセーフ性 も保証されています)

また、LocalDateTimeは「日付」「時間」の演算のいずれも可能ですが、
LocalDateなら「日付」のみ、LocalTimeなら「時間」のみ演算できるように
メソッドが用意されているため、これまた使い間違いがありません。

文字列変換はDateTimeFormatter

Dateクラスは、DateFormat(SimpleDateFormat)クラスで変換を行いましたが
JSR-310の日付クラスは、DateTimeFormatterクラスで変換を行います。

DateTimeFormatterはJavadocにも "This class is immutable and thread-safe" と明記されており
スレッドセーフが保証されているところがいいですね。
SimpleDateFormatがスレッドセーフでないと知らず、事故った 方もいることでしょう。


さて、このDateTimeFormatterクラスは、3つの作り方があります。
 1. DateTimeFormattersから選んで作る
 2. DateTimeFormattersから文字列で作る
 3. DateTimeFormatterBuilderできちんと作る


まずは1つめ、数は多くありませんが
ISO形式のフォーマットをいくつか選択することができます。

LocalDateTime dateTime = LocalDateTime.of(2012, 2, 3, 21, 30, 15, 123);

System.out.println(dateTime.toString(DateTimeFormatters.isoDateTime()));
System.out.println(dateTime.toString(DateTimeFormatters.isoDate()));
System.out.println(dateTime.toString(DateTimeFormatters.isoTime()));
System.out.println(dateTime.toString(DateTimeFormatters.isoWeekDate()));

実行結果

2012-02-03T21:30:15.000000123
2012-02-03
21:30:15.000000123
2012-W05-5

システムによってはこれで事足りることもあるでしょう。


しかし日本では、日付の区切りに - ではなく / を使うことのほうが多いですよね。
そういう場合、フォーマット指定を行うことができます。

LocalDateTime dateTime = LocalDateTime.of(2012, 2, 3, 21, 30, 15, 123);
System.out.println(dateTime.toString(DateTimeFormatters.pattern("yyyy/MM/dd HH:mm:ss")));

実行結果

2012/02/03 21:30:15

この方法が一番よく使われそうですね。


最後に、DateTimeFormatterBuilderの使い方も見ておきましょう。

LocalDateTime dateTime = LocalDateTime.of(2012, 2, 3, 21, 30, 15, 123);
DateTimeFormatter formatter = new DateTimeFormatterBuilder()
        .appendValue(ChronoField.YEAR)
        .appendLiteral("/")
        .appendValue(ChronoField.MONTH_OF_YEAR)
        .appendLiteral("/")
        .appendValue(ChronoField.DAY_OF_MONTH)
        .appendLiteral(" ")
        .appendValue(ChronoField.HOUR_OF_DAY)
        .appendLiteral(":")
        .appendValue(ChronoField.MINUTE_OF_HOUR)
        .appendLiteral(":")
        .appendValue(ChronoField.SECOND_OF_MINUTE)
        .toFormatter();

System.out.println(dateTime.toString(formatter));

実行結果

2012/02/03 21:30:15

記述が冗長になってしまうので、私はあまり好きではないのですが
間違いにくさという観点では、良いのでしょうかね?

誤ったFormatterを使うとどうなるの?

上の例では、LocalDateTimeに対するフォーマットを行ったので特に問題ないのですが、
たとえば「日付」に対して「時間」のフォーマットを行ったり、
「時間」に対して「日時」のフォーマットを行うと、情報が足りないわけですが、
その場合はどうなるのでしょうか。

早速試してみましょう。

DateTimeFormatter formatter = DateTimeFormatters.pattern("yyyy/MM/dd HH:mm:ss");
LocalTime.now().toString(formatter);

実行結果

Exception in thread "main" java.time.DateTimeException: Unsupported field: Year
at java.time.LocalTime.get0(LocalTime.java:670)
at java.time.LocalTime.getLong(LocalTime.java:647)
at java.time.format.DateTimePrintContext.getValue(DateTimePrintContext.java:261)
at java.time.format.DateTimeFormatterBuilder$NumberPrinterParser.print(DateTimeFormatterBuilder.java:1904)
at java.time.format.DateTimeFormatterBuilder$CompositePrinterParser.print(DateTimeFormatterBuilder.java:1567)
at java.time.format.DateTimeFormatter.printTo(DateTimeFormatter.java:335)
at java.time.format.DateTimeFormatter.print(DateTimeFormatter.java:307)
at java.time.LocalTime.toString(LocalTime.java:1540)
at DateTimeTest.main(DateTimeTest.java:17)
...

フォーマットに失敗して、DateTimeException が発生しました。
勝手に0000などで埋められるよりは、きっぱりと例外が発生したほうが
問題を検出しやすいので良いですね。

ちなみにフォーマットではなく、パースに失敗した時は、どうなるんでしょうか?

LocalTime.parse("2013-02-12");

実行結果

Exception in thread "main" java.time.format.DateTimeParseException: Text '2013-02-12' could not be parsed at index 2
at java.time.format.DateTimeFormatter.parseToBuilder(DateTimeFormatter.java:469)
at java.time.format.DateTimeFormatter.parse(DateTimeFormatter.java:372)
at java.time.LocalTime.parse(LocalTime.java:475)
at java.time.LocalTime.parse(LocalTime.java:460)
at Main.main(Main.java:15)
...

DateTimeParseException が発生しました。
実行時例外になっているのも、イマドキな感じで好感触ですね。

DBとの連携は?

最後に、気になるDBとの連携について。

Java8でアップデートされるJDBC 4.2では、
JSR-310の日付型などを扱えるようにするためのAPIが追加されました。

Javadocを見ると、setObjectメソッドが追加されていますね。
http://download.java.net/jdk8/docs/api/java/sql/PreparedStatement.html

setObjectだけでなく、setDate/setTime/setDateTimeにも
オーバーロードメソッドを追加してくれれば良かったのですが、
なぜ追加されなかったのでしょうかね? 余計に混乱を生むため?


ちなみにsetObjectメソッドはdefaultで実装されているため、
過去に独自PreparedStatementクラスを作っていた場合でも、
特にメソッド追加することなく流用することができますね。

Java5からJava6に上がった時は、
ConnectionやPreparedStatementインタフェースにメソッドが追加されたため、
過去に作った独自Connectionをうまく流用できないなど問題になった記憶がありますが、
Java8からはdefaultのおかげで、そういう問題を抑えることができそうです。

最後に

まずは今回のまとめです。

・Java8では日付、時間、日時が分かれたぞ!
・日付、時間、日時でそれぞれ習得、演算メソッドが分かれているので安全だぞ!
・日付系クラスも、DateTimeFormatterも、immutableでスレッドセーフだぞ!
・フォーマットは、DateTimeFormatters#patternメソッドから始めよう!
JDBC周りは、まぁまぁ。


また、今回のエントリーでは触れませんでしたが、
JSR-310ではタイムゾーンももちろん扱えますし、
期間を示す「Period」クラスや、和暦などの暦を扱う「Chronology」など
java.util.Dateになかった要素が取り込まれたり、使いづらかった点が改善されています。

そのせいでクラス数が多くなり、複雑になった面も否めませんが
日付が扱いやすくなったのは間違いないでしょうね。


それでは、またいずれ!

今日から始めるJava8

こんにちは id:cero-t です。
「なんとやらは風邪をひかない」と言われているところ
先日、インフルエンザに掛かってしまいまして。

重ね着+多段布団+電気毛布2枚のコンボで一気に悪寒を吹き飛ばし、
一日で熱を下げたものの、感染予防のために出社を控えたために時間でき、
ちょっとJava8などと戯れていました。

そう、今日はJava8の話題です。


今年の秋に正式リリースが予定されているJavaSE8ですが、
OpenJDKのサイトでは、既にEarly Access版を入手することができます。

JDK8 : http://openjdk.java.net/projects/jdk8/
ダウンロード : http://jdk8.java.net/download.html


1/31に、マイルストーン6がリリースされ Feature Complete となりました。
名前からすると 全機能開発完了版 という位置づけになりますね。

注目のLambda式や、interfaceのデフォルト実装、新しいDate and Time APIなどが
利用できるようになった一方、Lambdaの一部の機能や、
新しいJavaScript実行環境であるNashornなどは
2/21に予定されているマイルストーン7のリリースに延期されたため、まだ使えません。

それでもJava8の新機能を体験できる状態になっていますから、
今日はひとつ、これを使ってJava8を先取りしてみましょう。

いまならIntelliJ IDEA

マイルストーン版のJava8を触ってみようという皆さんですから、
さすがにインストールや環境変数の設定は、割愛しますね。

先ほども書きましたが、ダウンロードは、以下のサイトから。
Windows/Mac/Linuxのいずれでも大丈夫です。
JDK8ダウンロード : http://jdk8.java.net/download.html


開発環境は、Java8への対応が明言されているIntelliJ IDEAが良いでしょう。
EclipseではまだJava8の文法が使えませんが、IntelliJ IDEAなら今すぐ使えます。
無料のコミュニティ版でも、Java8に対応しています。
InnteliJ IDEA : http://www.jetbrains.com/idea/


ちなみにIDEAは、イデアではなくアイデアと読むそうですよ。
誰だよ、イデアって言い出したやつ。

Java8らしくLambda式を書いてみる

Java8を動かせる環境になったら、
早速、一番の注目株であるLambda式を書いてみましょうか。
Java8のLambda式とは、乱暴に言うと、匿名クラスを簡単に書くための仕組みです。

ちなみに、IntelliJ IDEAの場合、Moduleの設定画面にあるSourcesタブで
Language Levelを「8.0」にしておきましょう。
これを忘れていると、Lambda式など、Java8から導入された文法を書いた時にエラーが出てしまいます。


では、Lambda式を使って、
文字列を長さでソートするような処理を作ってみましょう。

public static void main(String[] args) {
    Comparator<String> comparator = (x, y) -> x.length() - y.length();

    String[] strings = {"abc", "abcde", "defg", "x", "oooooooo"};
    Arrays.sort(strings, comparator);

    System.out.println(Arrays.toString(strings));
}

ComparatorをLambda式で記述して、Arrays#sortでソートをしています。

これを実行してみると・・・

[x, abc, defg, abcde, oooooooo]

出ましたっ!
Java8がきちんと動いている証拠ですね。


ちなみにComparatorを事前定義せず、こんな風に書くこともできます。

public static void main(String[] args) {
    String[] strings = {"abc", "abcde", "defg", "x", "oooooooo"};
    Arrays.sort(strings, (x, y) -> x.length() - y.length());

    System.out.println(Arrays.toString(strings));
}

なんかもう別言語な雰囲気すらするLambda式ですが
この後、どんな風に世の中に受け入れられ ない るのか、要注目ですね。

Lambdaってどういう所で使えるの?

サクッと使ったLambda式ですが、どういう所で使えるのか少し説明しておきましょう。

Lambdaは「Functional Interface」と呼ばれる、
abstractメソッドが1つしかないinterfaceの実装を行うことができます。

たとえばRunnable(runメソッドのみ)や、
javafx.event.EventHandler(handleメソッドのみ)などがその例であり、
別スレッドに処理を委譲する所や、画面操作のイベントハンドリングを行う所などは
一番のLambdaの使いどころになります。


ただ、先の例で挙げたComparatorインタフェースには2つのメソッドが定義されています。
 ・int compare(T o1, T o2);
 ・boolean equals(Object obj);

なぜこれがLambdaで記述できるのでしょうか?


実はFunctional Interfaceが「abstractメソッド」と
みなすものには、いくつかのルールがあります。

 1. Objectクラスで実装されているpublicメソッドは、abstractメソッドとみなさない
 2. default修飾子(後述)で実装されているメソッドは、abstractメソッドとみなさない
 3. 複数のinterfaceを継承していて、シグニチャが一致するものは一つのメソッドとみなす

また、abstractクラスを使ってFunctional Interfaceを定義することもできません。


これに従うと、先のComparatorのequalsメソッドは1.のルールが適用され、
残ったcompareメソッドだけが唯一のabstractメソッドとみなされるわけです。


少し小難しい話になりましたが、
要するに、既にObjectやinterfaceで実装されているメソッドなどは、
Lambda式で書ける対象にはならないということです。

interfaceが実装を持つってどういうこと?

上で「default」だの、「interfaceで実装されている」だの書きましたが、
これはJava7までにはなかった考え方ですよね。


そう、Java8におけるもう一つの文法面の大きな変化として、
interfaceが実装を持てるようになった、ということが挙げられます。

interfaceが実装を持てるようになった背景を少しだけ説明しておきますと・・・

Java8ではParallel Array(Listを並行処理して高速化するもの)が導入され、
Collectionインタフェースに「parallelStream」メソッドが追加されました。

ただし、Collectionインタフェースにメソッドが追加されるということは、
これまで世の中の開発者が作成してきた独自のCollection実装を、
すべて改修しなければならない、ということになります。
Javaが大切にする「後方互換性」の観点から、
そのような改修を開発者に強いることは好ましくありません。

そのため、Java8よりインタフェースに実装を持てるようにして
Collection#parallelStreamメソッドにもデフォルト実装を持たせることで
過去に作った独自のCollection実装も、すべて使えるようにしたのです。

という、聞いた話を知ってた風に書いているので、
間違いがあれば、ぜひコメントで知らせてください m(_ _)m

早速、さくらばさんからコメントを頂いたので、反映しました!(><)


さて、そんなインタフェースのデフォルト実装ですが、早速試してみましょう。
こんなコードになります。

public static void main(String... args) {
    Calculator calculator = new Calculator() {
        public int add(int a, int b) {
            return a + b;
        }
    };

    System.out.println(calculator.add(3, 4));
    System.out.println(calculator.multi(3, 4));
}

static interface Calculator {
    int add(int a, int b);

    default int multi(int a, int b) {
        return a * b;
    }
}

multiメソッドの前にdefault修飾子がついていますが、
これでデフォルト実装を提供していることを示しています。

このCalculatorインタフェースでは、addにはデフォルト実装を提供せず、
multiだけにデフォルト実装を提供しているため、
利用時にはaddだけを実装すれば良くなります。


では、これで実行してみると・・・

7
12

きちんと計算されました。
利用側でmultiメソッドを実装しなくとも、
きちんとデフォルト実装が利用されているところがポイントですね。


interfaceがデフォルト実装を持てるようになったことで、
抽象クラスとの違いが、さらに分からなくなったというか
むしろ抽象クラス自体が要らなくなりそうですよね。

Javaを始めたばかりの若者に、interfaceと抽象クラスの違いを聞かれたら
どう答えれば良いものか、ちょっと悩ましいですね。

コメント頂きました:
抽象クラスは状態が持てますが、インタフェースでは状態が持てないため
今後も抽象クラスを雛形として使う方針は、変わらなさそうですね。

まとめ

・OpenJDKのサイトからJava8のEarly Access版をダウンロードできるぞ!
・開発環境を使うなら、Java8対応済みのIntelliJ IDEAがオススメだぞ!
・Lambdaもインタフェースのデフォルト実装も、Date and Time APIももう動かせるぞ!

それでは、エンジョイJava8!