在进行一些基本的lambda练习时,一个看似完全相同的匿名内部类的输出给我的输出与lambda不同。
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)
Lambda:
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
匿名类和lambda转换为以下中间表示形式:
@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); }
在lambda中,生成的方法参数标记为final,因为LambdaToMethod转换器将所有参数标记为FINAL(根据源代码 LambdaTranslationContext.translate(…):1899 )。
然后让表达式生成器检查变量标志,以及是否在最终变量时忽略临时变量生成(根据源代码 Lower.abstractRval(…):2277 ),因为认为禁止进行修改。
可能的解决方案:
从lamda生成的方法中的局部变量( 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