为什么Java 8中的lambdas不允许对匿名类没有的成员变量进行前向引用?

为什么Java 8中的lambdas不允许对匿名类没有的成员变量进行前向引用?

问题描述:

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

The following class contains a member variable runnable which is initialized with an instance of an anonymous inner class. The inner class references the same member:

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

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

This is not a problem as long as the method is not executed before the member has been assigned and the JLS allows such a reference.

理论上,成员变量的声明可以转换为lambda表达式,如下所示:

The declaration of the member variable could theoretically be converted to a lambda expression like this:

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

据我所知,这在功能上等同于前面的例子,但它被 javac 1.8.0_05 ,并显示以下错误消息:

To my understanding, this is functionally equivalent to the previous example, but it is rejected by javac 1.8.0_05 with the following error message:

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

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

While that statement is true, I do not see why this was disallowed. Was this intentionally disallowed, maybe because lambda expressions are compiled to different byte code which would lead to problems if it was allows? Or was just disallowed because there were already problems with these references when they were used in anonymous inner classes? Or was it unintentionally disallowed by the JLS writers? Or is it a bug in javac?

Bug#JDK-8027941 正是如此描述的。 Dan Smith(Project Lambda规范负责人)写道,这不是一个bug,不仅限于lambda。

Bug #JDK-8027941 describes exactly this. Dan Smith (Project Lambda Specification Lead) writes that it's not a bug, and not limited to lambdas.

一个相关的错误报告,他这样说:


8.3.2.3:首先,如果在现场声明之前使用,则通常禁止在字段初始化程序中使用字段。规范在这方面不是很清楚,但意图一直是之前包括字段自己的初始化程序。所以 int x = x + 1; 不是有效的字段声明。

8.3.2.3: First, a "use" of a field in a field initializer is generally prohibited if the use occurs before the field declaration. The spec is not very clear on this, but the intent has always been that "before" includes the field's own initializer. So "int x = x+1;" is not a valid field declaration.

他还说:


可以添加一个专门处理lambda体的功能,像匿名类的主体(或者,更一般地,如果它是变量初始化器,允许lambda引用它自己),但是这还没有完成。 (FWIW,8.3.2.3的直接调整并不完全安全,就像第4个子弹当前不是完全安全的那样:功能f =(功能)((功能)e - > f。 apply(e))。apply(null); 。)

It would possible to add a feature that would treat lambda bodies specially, like bodies of anonymous classes (or, more generally, allow a lambda to refer to itself if it is a variable initializer), but this has not been done. (FWIW, a straightforward tweak of 8.3.2.3 would not be entirely safe, just like the 4th bullet is not currently entirely safe: "Function f = (Function) ((Function) e -> f.apply(e)).apply(null);".)

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

I think the problem is that the designers of Java want to have simple, syntactic rules to decide what kind of statements are allowed, rather than depending on more complex, semantic code analysis. The benefit is probably a simpler spec and therefore less requirements on compilers, while the cost is that programmers can't express every program -- at least not in the way they want to.

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

As Marko Topolnik points out, there is a solution: fully qualify the field. Example from the bug report:

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); 

    /* ... */
}