问题 从lambda表达式引用final字段


最近我发现匿名类和lambda表达式之间存在细微差别:

public class FinalTest {
    final Runnable x = new Runnable() {
        @Override
        public void run() {
            System.out.println(x.hashCode());
        }
    };

    final Runnable y = () -> System.out.println(y.hashCode()); 
}

通常lambdas等同于匿名类。甚至我的Eclipse IDE都有重构来转换 x 到lambda(它变得完全像 y)并转换 y 到匿名课(它变得完全像 x)。然而lambda给我一个编译错误,而匿名类可以完美编译。错误消息如下所示:

>javac FinalTest.java
FinalTest.java:9: error: self-reference in initializer
    final Runnable y = () -> System.out.println(y.hashCode());
                                                ^
1 error

所以问题是:为什么会有这样的差异?


8001
2018-05-08 18:05


起源

我会的,但我现在的机器上没有JDK 8,这就是我把它扔到那里的原因。 - Ryan J
@TagirValeev为什么不发布错误信息,而不是强迫我们编译代码或猜测问题是什么? - JB Nizet
@JBNizet,添加了错误消息。 - Tagir Valeev
看到这份报告: bugs.openjdk.java.net/browse/JDK-8027941 - aioobe
假设“lambdas等同于匿名类”是你开始出错的地方。它们相似,但有一些显着差异。最大的区别在于对名称的解释。匿名者有一些非常复杂的范围规则(名称可以解决封闭的词法范围 要么 继承层次结构。)Lambda具有更简单的解析规则; lambda中未定义的名称与封闭词汇上下文中的名称具有完全相同的含义。在你的情况下,“x”是“this.x”的简写,而lambda中的“this”则表示将“this”括起来。 - Brian Goetz


答案:


这与此有关 JLS#8.3.3 处理前向引用。特别是,如果您使用完全限定名称,则会编译(因为该规则的第三个条件变为false 在C的实例变量初始值设定项或C的实例初始值设定项中,该用法是一个简单的名称):

final Runnable y = () -> System.out.println(this.y.hashCode());

在匿名类的情况下,第四个条件(C是封闭使用的最内层类或接口)不是真的,因为封闭类本身就是匿名类。


15
2018-05-08 18:14