Taste of Tech Topics

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

今日から始める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!