Lambda的行为与匿名内部类不同

在做一些基本的lambda练习时,一个看似相同的匿名内部类的输出给了我一个不同于lambda的输出。

interface Supplier { T get(T t); } 

场景#1

 Supplier s1 = new Supplier() { @Override public Integer get(Integer t) { return t; } }; Supplier s2 = t -> t; System.out.println(s1.get(2)); System.out.println(s2.get(2)); 

输出22 。 这里没什么新鲜的。


但是当我这样做时:

场景#2

 Supplier s1 = new Supplier() { @Override public Integer get(Integer t) { return t++; } }; Supplier s2 = t -> t++; System.out.println(s1.get(2)); System.out.println(s2.get(2)); 

输出23

问题:两个输出不应该相同吗? 我错过了什么吗?


为了完整起见: 场景#3

 Supplier s1 = new Supplier() { @Override public Integer get(Integer t) { return ++t; } }; Supplier s2 = t -> ++t; System.out.println(s1.get(2)); System.out.println(s2.get(2)); 

输出33 。 这里没什么新东西。

更新:仍然从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方法不会复制参数(load – > unbox – > add 1 – > box – > store – > load – > return)。

UPDATE

这肯定是一个javac bug。

我从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 ),因为修改被认为是禁止的。

可能的解决方案:

  1. 禁止在lambda或lambda中修改参数
  2. 在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