【问题标题】:Lambda behaving differently than anonymous inner classLambda 的行为与匿名内部类不同
【发布时间】:2014-03-25 22:57:29
【问题描述】:

在做一些基本的 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));

输出 22。这里没有什么新鲜事。


但是当我这样做时:

场景 #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));

输出 23

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


为了完整起见: 场景 #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));

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

更新:仍然从 1.8.0-b132 获得相同的输出

更新 #2:错误报告: https://bugs.openjdk.java.net/browse/JDK-8038420

更新 #3:javac 中的错误已修复,您现在应该可以获得相同的结果。

【问题讨论】:

  • 我得到 2 和 2 作为你的第二个 sn-p。
  • +1 表示无法复制
  • 嗯,这很有趣。我们是否在这里查看与 JVM 版本/构建相关的内容?我正在使用构建 1.8.0-ea-b81。目前正在下载更新的版本。将发布更新。
  • 我在 MacOS X Mavericks 上重现了该错误。 java -version 的输出:java 版本“1.8.0”Java(TM) SE 运行时环境(构建 1.8.0-b132)Java HotSpot(TM) 64 位服务器 VM(构建 25.0-b70,混合模式)
  • 我可以用 Java(TM) SE Runtime Environment (build 1.8.0-ea-b118) 重现它,但不能在任何 JRE8 Eclipse 使用的地方重现。

标签: java lambda java-8 anonymous-inner-class


【解决方案1】:

根据生成的字节码:

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

可能的解决方案:

  1. 禁止在 lambda 内修改参数或
  2. 从局部变量中移除 FINAL 标志 (LambdaTranslationContext.translate(…):1894) 和 lamda 生成方法中的参数 (LambdaTranslationContext.translate(…):1899):

     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

【讨论】:

猜你喜欢
  • 2016-10-27
  • 1970-01-01
  • 2019-04-02
  • 1970-01-01
  • 1970-01-01
  • 2020-05-02
  • 2017-12-31
  • 1970-01-01
  • 2014-05-03
相关资源
最近更新 更多