Fight the Future

Java言語とJVM、そしてJavaエコシステム全般にまつわること

Project Lambdaの変更差分をまとめてみる

関ジャバでさくらばさんにProject Lambdaの最新情報について話していただけました。

僕も以前調べていました。

Java SE 8に導入されるのは、ほんとにクロージャ!? - Fight the Future

http://jyukutyo.hatenablog.com/entry/20111121/1321891230

この頃から、変わった部分ももちろんあるので、それについてまとめてみます。

でも正直に言って、さくらばさんのブログを読んだほうが詳しいし、正確です。

Java Advent Calendar 1 日目 - Project Lambda の遅延評価

http://www.javainthebox.com/2012/12/java-advent-calendar-1-project-lambda.html

ここでは僕の備忘録代わりにまとめます。

Project LambdaつきJDK

Java Platform, Standard Edition 8 Early Access Releases — Java.net

http://jdk8.java.net/lambda/

相変わらず、JDK7 + Project Lambdaなので、Java SE 8のその他の新機能はついてません。

defaultキーワードの位置

void remove() default {
    throw new UnsupportedOperationException("remove");
}

とメソッド名の後ろだったのが、

default void remove() {
    throw new UnsupportedOperationException("remove");
}

となります。 ただし、2012/12/03現在のものでは、まだ以前の位置のままです。

Listインタフェースからmap()メソッドがなくなった?

以前は

list.map(x -> x * 2)

という風にバルク操作を呼び出せていました。 今はこれだとコンパイルエラーになります。

Streamの概念が取り入れられています。 Scalaにあるものと同様のものです。

Streamとは

Streamとはイテレータに似て、空になるまで値を引き出すことができます。しかし、実際に次の要素を要求しない限り、操作が行われることはありません。 つまり、遅延を実現するためのものです。これにより無限のリストを扱うことができます。

なので、

list.stream().map(x -> x * 2)

のようにStreamオブジェクトを取得して呼び出します。 複数の処理を連続して呼び出せます。

list.stream().map(x -> x * 2).reduce(0, (t, n) -> t + n)

ただし、map()メソッドは遅延するため、map()メソッドを呼び出した時に処理が実行されるません。実際には即時評価されるメソッドであるreduce()メソッドを呼び出した時にmap()引数として渡していた処理が実行されます。

Iterablesクラス

もともと、Listでmap()メソッドを呼び出せていたのは、java.util.Iterablesクラスを継承していたからでした。 もうListはIterablesクラスを継承していません。 もともと、Listでmap()メソッドを呼び出せていたのは、Iterableインタフェースにmap()メソッドのデフォルト実装があり、そこでユーティリティクラスであるjava.util.Iterablesクラスのメソッドに委譲していました。今はIterableインタフェースからバルク操作メソッドは取り除かれています。 Iterablesクラスはほとんどコメントアウトされていて、残っているのはcount()メソッドだけです。

インナークラスのclassファイルを生成しなくなった?

ラムダ式を書くと、それはSAMタイプ(Single Abstract Method)のシンタックスシュガーなわけで、今まではインナークラスのclassファイルが生成されていました。あの$がつくやつね。 しかし、今のバージョンでは、ラムダ式の処理をinvokedynamicを利用して呼び出します。

試しにラムダ式を書いて、「javap -v」します。 すると、出ました!invokedyamic!

58: invokedynamic #7,  0              // InvokeDynamic #0:lambda:()Ljava/util/functions/Mapper;

うーん、でもどうしてinvokedynamicを使うんだろう、と疑問に思いました。

すばらしいエントリがありました。

Lambda 式に invokedynamic を使うのかもしれない話 - miyakawa_takuの日記

http://d.hatena.ne.jp/miyakawa_taku/20120728/1343478485

Lambda 式は匿名クラスのインスタンス生成と同じバイトコードを生成します。これには性能面の欠点があります。たとえば、コンパイル時に Lambda 式ごとにクラスが作られるので、 Lambda 式をたくさん使うとクラスロードが大変になります。

なるほど…そしてinvokedynamicを使うことで、初回実行時に呼び出されるブートストラップメソッドが、匿名クラス方式か、MethodHandleの呼び出しにするのかを選択できます。 invokedynamicは呼び出す処理を実行時に変えられるからです。

MethodHandleは合成したり部分適用したりできるんですが、匿名クラスに比べれば呼び出しは遅くなります。そりゃそうか。