基本的なラムダ演習を行っている間、明らかに同一の匿名の内部クラスからの出力はラムダとは異なる出力を与えていました。
interface Supplier<T> {
T get(T t);
}
シナリオ#1
Supplier<Integer> s1 = new Supplier<Integer>() {
@Override
public Integer get(Integer t) {
return t;
}
};
Supplier<Integer> s2 = t -> t;
System.out.println(s1.get(2));
System.out.println(s2.get(2));
出力2および2。ここには何も新しいものはありません。
しかし、これを行うと:
シナリオ#2
Supplier<Integer> s1 = new Supplier<Integer>() {
@Override
public Integer get(Integer t) {
return t++;
}
};
Supplier<Integer> s2 = t -> t++;
System.out.println(s1.get(2));
System.out.println(s2.get(2));
出力2および3
質問:両方の出力を同一にすべきではありませんか?何か不足していますか?
完全を期すために、シナリオ#3
Supplier<Integer> s1 = new Supplier<Integer>() {
@Override
public Integer get(Integer t) {
return ++t;
}
};
Supplier<Integer> s2 = t -> ++t;
System.out.println(s1.get(2));
System.out.println(s2.get(2));
出力3および3。ここにも新しいことはありません。
更新:1.8.0-b132から同じ出力を取得
アップデート#2:バグレポート: https : //bugs.openjdk.java.net/browse/JDK-8038420
アップデート#3:バグはjavacで修正されました。これで同じ結果が得られるはずです。
生成されたバイトコードによると:
Java(TM)SEランタイム環境(ビルド1.8.0-b132)
ラムダ:
private static java.lang.Integer lambda$main$0(java.lang.Integer);
descriptor: (Ljava/lang/Integer;)Ljava/lang/Integer;
flags: ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
Code:
stack=2, locals=2, args_size=1
0: aload_0
1: invokevirtual #9 // Method java/lang/Integer.intValue:()I
4: iconst_1
5: iadd
6: invokestatic #6 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
9: dup
10: astore_0
11: astore_1
12: aload_0
13: areturn
LineNumberTable:
line 20: 0
LocalVariableTable:
Start Length Slot Name Signature
0 14 0 t Ljava/lang/Integer;
匿名クラス:
public java.lang.Integer get(java.lang.Integer);
descriptor: (Ljava/lang/Integer;)Ljava/lang/Integer;
flags: ACC_PUBLIC
Code:
stack=2, locals=4, args_size=2
0: aload_1
1: astore_2
2: aload_1
3: invokevirtual #2 // Method java/lang/Integer.intValue:()I
6: iconst_1
7: iadd
8: invokestatic #3 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
11: dup
12: astore_1
13: astore_3
14: aload_2
15: areturn
LineNumberTable:
line 16: 0
LocalVariableTable:
Start Length Slot Name Signature
0 16 0 this LTest$1;
0 16 1 t Ljava/lang/Integer;
ご覧のとおり、匿名クラスでは、ローカル変数テーブル(メソッドパラメーターt)から変数を読み込んだ後、パラメーターのランタイムコピーを別の変数(astore_2)に格納し、このパラメーターのコピーを戻り値として使用します。
Lambdaメソッドはパラメーターのコピーを作成しません(ロード->ボックス解除-> 1を追加->ボックス->ストア->ロード->リターン)。
更新
これは間違いなくjavacのバグです。
私はhttp://hg.openjdk.java.net/jdk8u/jdk8uからソースを取得しました
匿名クラスとラムダは、次の中間表現に変換されます。
@Override()
public Integer get(Integer t) {
return (let /*synthetic*/ final Integer $112619572 = t in
(let /*synthetic*/ final Integer $1295226194 = t = Integer.valueOf((int)(t.intValue() + 1)) in $112619572));
}
/*synthetic*/ private static Integer lambda$main$0(final Integer t) {
return (let /*synthetic*/ final Integer $1146147158 = t = Integer.valueOf((int)(t.intValue() + 1)) in t);
}
LambdaToMethodトランスレーターはすべてのパラメーターをFINALとしてマークするため(ソースコードLambdaTranslationContext.translate(…):1899によると)、ラムダでは、最終としてマークされたメソッドパラメーターがマークされます。
次に、式ビルダーが変数フラグをチェックし、最終的なものである場合は一時変数の生成を省略します(ソースコードLower.abstractRval(…):2277によると)。これは、変更が禁止されていると見なされているためです。
可能な解決策:
ローカル変数(LambdaTranslationContext.translate(…):1894)およびラムダ生成メソッドのパラメーター(LambdaTranslationContext.translate(…):1899)からFINALフラグを削除します。
case LOCAL_VAR:
ret = new VarSymbol(FINAL, name, types.erasure(sym.type), translatedSym);
...
case PARAM:
ret = new VarSymbol(FINAL | PARAMETER, name, types.erasure(sym.type), translatedSym);
...
私はFINALフラグを削除し、テストから期待される結果を得ました:https : //bugs.openjdk.java.net/browse/JDK-8038420
この記事はインターネットから収集されたものであり、転載の際にはソースを示してください。
侵害の場合は、連絡してください[email protected]
コメントを追加