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

时间:2014-09-11 12:10:27

标签: java eclipse maven lambda java-8

我使用的是Java 8 Update 20 32位,Maven 3.2.3,Eclipse Luna Build id:20140612-0600 32位。

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

这些错误仅在我使用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] variable panel might not have been initialized

虽然错误消息非常清楚正在发生的事情(编译器认为在实例化之前调用了最终变量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();
    }
}

我注意到另外两个与此问题有关的奇怪事情:

  1. Eclipse在自动编译类时不会报告此错误。 Eclipse使用与maven相同的JDK来编译类。
  2. 如果我使用maven使用匿名类编译类,那么我将类更改为使用lambdas并再次使用maven编译它,它不会报告错误。在这种情况下,如果我使用mvn clean后跟mvn compile
  3. ,它只会再次报告错误

    有人可以帮我解决这个问题吗?或者尝试重现这个问题?

1 个答案:

答案 0 :(得分:8)

  

虽然错误信息非常清楚发生了什么(编译器认为最终变量“panel”在实例化之前被调用),但是在按钮生成动作之前不会调用该变量,我们如何能够要说当动作发生时,代码应该编译。

您应该考虑编译器遵循正式规则并且不了解您的事实。特别是,编译器无法知道方法addActionListener不会立即调用actionPerformed方法。它也不知道按钮的可见性,它确定何时可以调用actionPerformed

正式行为has a specification。在那里你会发现以下几点:

Chapter 16. Definite Assignment

  

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

     

对其值的访问包括变量的简单名称(或者,对于字段,由this限定的字段的简单名称),在表达式中的任何位置发生,除了左边的操作数简单赋值运算符=(第15.26.1节)。

15.27.2. Lambda Body

  

...

     

与匿名类声明中出现的代码不同,名称的含义以及出现在lambda主体中的thissuper关键字以及引用声明的可访问性与周围的相同上下文(lambda参数引入新名称除外)。

在你的代码中,lambda体内名称的含义,即panel的读取,与构造函数周围的上下文相同。在该上下文中,“每个空白final字段在对其值进行任何访问时必须具有明确赋值值的规则”适用。

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