【问题标题】:Why do lambdas in Java 8 disallow forward reference to member variables where anonymous classes don't?为什么 Java 8 中的 lambdas 不允许前向引用匿名类不允许的成员变量?
【发布时间】:2014-07-01 11:42:58
【问题描述】:

以下类包含一个成员变量runnable,它使用匿名内部类的实例进行初始化。内部类引用相同的成员:

class Example {
    Runnable runnable = new Runnable() {
        @Override
        public void run() {
            System.out.println(runnable);
        }
    };
}

只要在分配成员之前不执行方法并且 JLS 允许这样的引用,这不是问题。

成员变量的声明理论上可以转换成这样的 lambda 表达式:

Runnable runnable = () -> System.out.println(runnable);

据我了解,这在功能上等同于前面的示例,但被javac 1.8.0_05 拒绝并显示以下错误消息:

Error:(2, 54) java: self-reference in initializer

虽然该声明是正确的,但我不明白为什么不允许这样做。这是故意不允许的,可能是因为 lambda 表达式被编译为不同的字节码,如果允许的话会导致问题?或者只是因为这些引用在匿名内部类中使用时已经存在问题而被禁止?还是 JLS 的作者无意中不允许这样做?还是javac 中的错误?

【问题讨论】:

  • 在 Eclipse 中,这有效:Runnable runnable = () -> System.out.println(this.runnable); 如果没有添加 this 限定符,它只会失败。

标签: lambda java-8 javac


【解决方案1】:

Bug #JDK-8027941 正是描述了这一点。 Dan Smith(项目 Lambda 规范负责人)写道,这不是错误,并且不仅限于 lambda。

a related bug report的cmets中,他是这样说的:

8.3.2.3:首先,如果使用发生在字段声明之前,则通常禁止在字段初始值设定项中“使用”字段。规范对此不是很清楚,但意图一直是“之前”包含该字段自己的初始化程序。所以“int x = x+1;”不是一个有效的字段声明。

他还说:

可以添加一个特殊处理 lambda 主体的功能,例如匿名类的主体(或者,更一般地说,如果 lambda 是变量初始化器,则允许它引用自身),但这还没有完成。 (FWIW,8.3.2.3 的直接调整不会完全安全,就像第 4 个项目符号目前并不完全安全:“Function f = (Function) ((Function) e -> f.apply(e)).apply(null);”。)

我认为问题在于 Java 的设计者希望有简单的句法规则来决定允许什么样的语句,而不是依赖于更复杂的语义代码分析。好处可能是更简单的规范,因此对编译器的要求更少,而代价是程序员不能表达每个程序——至少不是以他们想要的方式。


正如 Marko Topolnik 指出的那样,有一个解决方案:完全限定该领域。错误报告中的示例:

import java.util.function.Function; 

public class LambdaSelfRef { 

    // COMPILATION FAILURE 
    public static Function<Object, Object> op1 = e -> op1.apply(e); 

    // COMPILES OK 
    public static Function<Object, Object> op2 = e -> LambdaSelfRef.op2.apply(e); 

    /* ... */
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-05-10
    • 1970-01-01
    • 2017-12-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多