さきほどの記事で Lambda 式を「構文糖」と書きましたが、実はそうじゃないのかもしれません。 Java 7 で追加された invokedynamic 命令を使って、 Lambda 式の実行を効率化する、という話があるからです。
以下、 Brian Goetz の From Lambdas to Bytecode という資料から理解できた範囲を書きます。 2011 年に開催された JVM Language Summit で発表されたものみたいです。現時点での仕様のドラフトは JSR 335 の添付文書 metafactory.pdf をご覧ください。また、 invokedynamic については 2月の JJUG Night Seminar で使った Java 7 invokedynamic の概要をお薦めします。
例として、次のようなコードを考えてみます。
// リストの要素を倍にして戻す Iterable<Integer> doubleList() { return numbers.map(n -> n * 2); }
現時点のビルドでは、 Lambda 式は匿名クラスのインスタンス生成と同じバイトコードを生成します。これには性能面の欠点があります。たとえば、コンパイル時に Lambda 式ごとにクラスが作られるので、 Lambda 式をたくさん使うとクラスロードが大変になります。たとえば、上述のコードは次のようにコンパイルされます。
// 現状は、匿名クラスのインスタンス生成と同じ Iterable<Integer> doubleList() { return numbers.map(new $AnonymousMapper$()); } // Lambda 式ごとにこんなクラスが作られる class $AnonymousMapper$ implements Mapper<Integer, Integer> { Integer map(Integer n) { return n * 2; } }
対案としては、 Lambda 式の中身を static メソッドにして、この static メソッドを呼び出す MethodHandle を作り、それを元のインタフェースでラップする、という方法が考えられます。この方式の問題は、 MethodHandle の呼び出しが、まだチューニングされていないので、相当遅いということです。
// MethodHandle を使う Iterable<Integer> doubleList() { MethodHandle mh = MethodHandles.lookup() .findStatic(getClass(), "$anonymousMapper$", methodType(Integer.class, Integer.class)); Mapper mapper = MethodHandles.asInstance(mh, Mapper.class); return numbers.map(mapper); } static Integer $anonymousMapper$(Integer n) { return n * 2; }
ということで、しばらくは匿名クラス方式がいいけど、いずれは MethodHandle 方式に移行したい。でも、匿名クラスのインスタンスを作る処理をバイトコードに直接書いてしまうと、どこかの時点で Lambda 式を使ったコードを再コンパイルしてもらう必要がある。これはかっこ悪いです。
そこで、 invokedynamic を使えば、どちらの方式で行くのかを、コンパイル時ではなく実行時に決められるじゃん!というのが Goetz の言い分です。このやり方では、 Lambda 式は invokedynamic 命令の呼び出しに変換され、初回実行時に呼び出されるブートストラップメソッドが、どちらの方式で行くのかを決めます。
// 選択をブートストラップメソッドに任せる。最早 Java ソースでは書けない Iterable<Integer> doubleList() { Mapper mapper = <invokedynamic LambdaMetaFactory#metaFactory, "$anonymousMapper$"> return numbers.map(mapper); } // 匿名クラス経由、あるいは MethodHandle 経由で呼ばれる static Integer $anonymousMapper$(Integer n) { return n * 2; }
上のコードで、 LambdaMetaFactory#metaFactory は JVM 実装が提供します。このブートストラップメソッドは、しばらくの間は匿名クラスを実行時に生成して、そのインスタンスを作る戦略を選ぶでしょうし、いずれ MethodHandle がカリカリチューンされたら、 MethodHandle を生成する戦略を選ぶようにするでしょう。その際、 Lambda 式を使っている既存のソースを再コンパイルする必要はありません。
さあどうだ。素敵じゃない?