ラムダ式がどのように実現されているかを確認してみました。
背景
Java8で新仕様として「ラムダ式」が追加されました。そのラムダ式がどのように実現されているか、確認してみました。
簡単な確認
Java7以前
比較のために、まずJava7以前のコードを以下のように書いてみます。Java7まではメソッドの引数として、無名クラスを作って渡してあげます。
(他にもRunnableとかListenerとかにも多い記法。)
[ec2-user@xx java]$ cat jp/namihira/util/SortClass.java /** * Copyright 2014 Kosuke Namihira All Rights Reserved. */ package jp.namihira.util; import java.util.Arrays; import java.util.Comparator; public class SortClass { public static void main(String[] args) { // preapre String[] strs = new String[]{"a", "ccc", "bb"}; // action Arrays.sort(strs, new Comparator<String>() { @Override public int compare(String first, String second) { return Integer.compare(first.length(), second.length()); } }); // check System.out.println(Arrays.asList(strs)); } }
で、上記コードをコンパイルして出来たクラスファイルを覗いてみます。
実装した通り、コンストラクタとmainメソッドがあることがわかります。
[ec2-user@xx java]$ javap -private jp/namihira/util/SortClass.class Compiled from "SortClass.java" public class jp.namihira.util.SortClass { public jp.namihira.util.SortClass(); public static void main(java.lang.String[]); }
Java8
次に、Java8でのラムダ式を使って同じロジックを書いてみます。Arrays.sortメソッドの引数のComparetorは関数型インタフェースなので、ラムダ式で書くことができます。
[ec2-user@xx java]$ cat SortLambda.java /** * Copyright 2014 Kosuke Namihira All Rights Reserved. */ package jp.namihira.util; import java.util.Arrays; public class SortLambda { public static void main(String[] args) { // preapre String[] strs = new String[]{"a", "ccc", "bb"}; // action Arrays.sort(strs, (first, second) -> Integer.compare(first.length(), second.length())); // check System.out.println(Arrays.asList(strs)); } }
同じようにコンパイルしてクラスファイルを覗いてみます。
[ec2-user@xx java]$ javap -private jp/namihira/util/SortLambda.class Compiled from "SortLambda.java" public class jp.namihira.util.SortLambda { public jp.namihira.util.SortLambda(); public static void main(java.lang.String[]); private static int lambda$main$0(java.lang.String, java.lang.String); }
なんかメソッド増えとる( ゚д゚ )「private static int lambda$main$0(java.lang.String, java.lang.String);」
ここでもう結論ですが、
のようです。*1
「ラムダ式」という新たな記法ですが、イメージ的にはこんな捉え方でいいと思う(´・ω・`)b
もう少し詳細に見てみる
先ほどのラムダ式のクラスファイルをデコンパイルして、もっと詳細に見てみます。
CFR - yet another java decompiler.を使ってデコンパイルしてみました。
以下のように出力されました。
※そのままデコンパイルするとラムダ記法までもどっちゃうので、オプション「decodelambdas」を付けてます。
[ec2-user@xx java]$ java -jar cfr_0_90.jar jp/namihira/util/SortLambda.class --decodelambdas false /* * Decompiled with CFR 0_90. */ package jp.namihira.util; import java.io.PrintStream; import java.util.Arrays; public class SortLambda { public static void main(String[] arrstring) { String[] arrstring2 = new String[]{"a", "ccc", "bb"}; Arrays.sort(arrstring2, (java.util.Comparator)LambdaMetafactory.metafactory(null, null, null, (Ljava/lang/Object;Ljava/lang/Object;)I, lambda$main$0(java.lang.String java.lang.String ), (Ljava/lang/String;Ljava/lang/String;)I)()); System.out.println(Arrays.asList(arrstring2)); } private static /* synthetic */ int lambda$main$0(String string, String string2) { return Integer.compare(string.length(), string2.length()); } }
まず分かるのは、確かにラムダ式の処理が書かれたメソッドが新規に定義されていることがわかります(lambda$main$0)。
問題なのは呼び出し元で、結構複雑です。。。
以下のクラスを使っています。
https://docs.oracle.com/javase/jp/8/api/java/lang/invoke/LambdaMetafactory.htm
ラムダ式が呼ばれるまでの流れ
LambdaMetafactory.metafactoryメソッドの引数の雰囲気としては以下のような感じです。(一部のみ)
metafactoryメソッドの引数 | コンパイル後の表現 |
---|---|
呼び出すメソッド(ラムダ式)の実態 | lambda$main$0 |
メソッド(ラムダ式)の引数と戻り値(MethodType) | (String、String、Integer) |
作成予定の関数インタフェースのための引数と戻り値(MethodType) | Object、Object、Integer・・・Comparatorクラスのint compare(T o1, T o2)に相当 |
LambdaMetafactory.metafactoryメソッドを簡単に言うと*2、関数型インタフェースのインスタンスを生成してくれるオブジェクトを作成してくれます。
インスタンスを生成するまでには、Java7で追加された「invokedynamic(動的メソッド呼び出し)」と「メソッドハンドル(Java7版リフレクション)」を使っています。
全体的な流れ(コンパイルから実行まで)は以下の通りです。
- ソースコードのコンパイル時に、ラムダ記法の部分を「LambdaMetafactory.metafactory」と「内部メソッド(lambda$XX$0)」で置き換える。
- コードを実行すると、LambdaMetafactory.metafactoryに対して、「内部メソッド名」「内部メソッドの型情報(引数や戻り値)」「作成する関数型インターフェースのメソッドの型情報(引数や戻り値)」を渡すことで、「関数型インタフェースのインスタンスを作ることのできるメソッドハンドル」をもつオブジェクト(CallSite)が動的に取得される。※invokedynamic
- そのオブジェクトが保持しているメソッドハンドルを呼ぶことで、関数型インタフェースのインスタンスが生成される。※メソッドハンドル
- その関数型インタフェースのインスタンスが、呼び出し先のメソッド(Arrays.sort)に渡される。
#ということは実際の動作は先ほどのラムダ式のところに来たら新規に定義されたメソッドが呼ばれるというのではなくて、そのメソッド自体はインスタンス生成のための情報に過ぎないということだろう(´Д`)結局。
まとめ
ラムダ式の動きが、な、なんとなく分かった気がする(;´Д`)参考
CFR で Java 8 のラムダ式をデコンパイルする - なんとなくな Developer のメモ
LambdaMetafactory (Java Platform SE 8)
Comparator (Java Platform SE 8)
Javaプログラマーなら習得しておきたい Java SE 8 実践プログラミング
- 作者: Cay S. Horstmann,柴田芳樹
- 出版社/メーカー: インプレス
- 発売日: 2014/09/22
- メディア: 単行本(ソフトカバー)
- この商品を含むブログ (5件) を見る