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

时间:2014-07-01 11:42:58

标签: lambda java-8 javac

以下类包含一个成员变量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中的错误?

1 个答案:

答案 0 :(得分:25)

Bug #JDK-8027941正好描述了这一点。丹·史密斯(项目Lambda规范主管)写道,这不是一个错误,不仅限于lambdas。

a related bug report的评论中,他这样说:

  

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

    /* ... */
}