在Java 8中使用lambdas时出现意外错误

在Java 8中使用lambdas时出现意外错误,java,eclipse,maven,lambda,java-8,Java,Eclipse,Maven,Lambda,Java 8,我正在使用Java8更新20 32位,Maven 3.2.3,EclipseLuna构建id:20140612-0600 32位 在开始使用lambdas之后,我的项目中的一些类开始在maven中报告编译错误(mvncile) 这些错误仅在使用lambdas时出现。如果我切换回匿名类,错误就会消失 我可以用一个简单的测试用例重现错误: package br; import java.awt.Button; import java.awt.Panel; public class Test {

我正在使用Java8更新20 32位,Maven 3.2.3,EclipseLuna构建id:20140612-0600 32位

在开始使用lambdas之后,我的项目中的一些类开始在maven中报告编译错误(
mvncile

这些错误仅在使用lambdas时出现。如果我切换回匿名类,错误就会消失

我可以用一个简单的测试用例重现错误:

package br;

import java.awt.Button;
import java.awt.Panel;

public class Test {

    private final Button button;
    private final Panel panel;

    public Test() {
        button = new Button();
        button.addActionListener(event -> {
            System.out.println(panel);
        });
        panel = new Panel();
    }
}
我是这样编译的:

mvn clean;mvn compile
我得到了这个错误:

[ERROR]/C:/Users/fabiano/workspace luna/Test/src/main/java/br/Test.java:[14,44]变量面板可能尚未初始化
虽然错误消息非常清楚发生了什么(编译器认为在实例化前调用了最终变量
panel
),但直到按钮生成一个操作,并且我们无法确定操作何时发生,代码才应该编译,否则将不会调用该变量。事实上,如果我不使用lambdas,它会按照应该的方式编译:

package br;

import java.awt.Button;
import java.awt.Panel;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

public class Test {

    private final Button button;
    private final Panel panel;

    public Test() {
        button = new Button();
        button.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                System.out.println(panel);
            }
        });
        panel = new Panel();
    }
}
我注意到与此问题相关的另外两件奇怪的事情:

  • Eclipse在自动编译类时不报告此错误。Eclipse使用与maven相同的JDK来编译该类
  • 如果我使用maven使用匿名类编译该类,那么我将该类更改为使用lambdas并再次使用maven编译它,它不会报告错误。在这种情况下,如果我使用
    mvnclean
    后跟
    mvncile
    ,它只会再次报告错误
  • 有人能帮我解决这个问题吗?还是试图重现这个问题

    虽然错误消息非常清楚发生了什么(编译器认为最终变量“panel”在实例化之前被调用),但直到按钮生成一个动作,以及我们如何说动作何时发生,该变量才会被调用,代码应该编译

    你应该考虑编译器遵循正式规则而没有知识的事实。尤其是,编译器无法知道方法
    addActionListener
    不会立即调用
    actionPerformed
    方法。它也不知道按钮的可见性,该按钮决定何时调用
    actionPerformed

    正式的行为。在这里,您可以找到以下几点:

    每个局部变量(§14.4)和每个空白
    final
    字段(§4.12.4,§8.3.1.2)在访问其值时必须具有明确的赋值

    对其值的访问包括变量的简单名称(或者,对于字段,由
    this
    限定的字段的简单名称),出现在表达式中任何位置,但作为简单赋值运算符
    =
    的左操作数除外(§15.26.1)

    与匿名类声明中出现的代码不同,lambda正文中出现的名称和
    this
    super
    关键字的含义以及引用声明的可访问性与周围上下文中的含义相同(除了lambda参数引入新名称)

    在您的代码中,lambda主体中的名称的含义(读取
    面板
    )与周围上下文中的名称含义相同,后者是构造函数。在这种情况下,适用的规则是“当对其值进行任何访问时,每个空白的
    final
    字段必须有一个明确的赋值”


    是的,这与内部类定义不同。规范明确指出。

    首先,在实例构造期间不应添加侦听器。这会导致引用泄漏,被认为是并发编程中非常糟糕的做法。现在,关于你最后的领域。它显然是在创建引用此面板的侦听器之后初始化的。为什么不能在添加操作侦听器之前初始化面板字段?Panel字段是最终字段,必须在构造函数中进行初始化。@mkrakhin有引用吗?我看不出这会导致泄漏。即使这样,为什么在构造函数中或在另一个方法中会有不同呢?因为最终字段冻结只发生在实例构造结束时。在这种情况下,代码是安全的,至少只要在构造函数中不显示按钮(从而能够被点击)(这无论如何都不应该发生,因为Swing不是线程安全的)@Holger,谢谢你的回答,现在我很清楚lambda与匿名类相比有不同的行为。我认为编译器只是在引擎盖下“替换”了相应匿名类的lambda,行为应该是相同的。我的错。你是对的。我认为,因为lamdba在引擎盖下工作的方式,一个ref参考字段(如面板)实际上是在创建lamdba时访问的。@zeroflagL:这取决于实现,更确切地说是编译器的决定。使用current
    javac
    测试表明,它在创建时不是访问
    panel
    ,而是像任何非
    final
    字段一样读取它。但是其他编译器可能会做不同的事情无论如何,这在Eclipse中对我很有效,直到我使用Maven进行编译才失败