a can only be final here. Why? How can I reassign a in onClick() method without keeping it as private member? private void f(Button b, final int a){ b.addClickHandler(new ClickHandler() { @Override public void onClick(ClickEvent event) { int b = a*5; } }); } How can I return the 5 * a when it clicked? I mean, private void f(Button b, final int a){ b.addClickHandler(new ClickHandler() { @Override public void onClick(ClickEvent event) { int b = a*5; return b; // but return type is void } }); }


当前回答

匿名内部类中的方法可以在生成该类的线程终止后调用。在您的示例中,内部类将在事件分派线程上调用,而不是在创建它的线程中调用。因此,变量的作用域是不同的。所以为了保护这样的变量赋值范围问题,你必须声明它们为final。

其他回答

匿名类是一个内部类,严格的规则适用于内部类(JLS 8.1.3):

任何在内部类中使用但未声明的局部变量、形式方法参数或异常处理程序参数必须声明为final。在内部类中使用但未声明的任何局部变量必须明确地在内部类的主体之前赋值。

我还没有在jls或jvm上找到一个原因或解释,但我们知道,编译器为每个内部类创建了一个单独的类文件,它必须确保,在这个类文件上声明的方法(在字节码级别上)至少可以访问局部变量的值。

(Jon有完整的答案-我保留这一个未删除,因为有人可能对JLS规则感兴趣)

要理解这种限制的基本原理,请考虑以下程序:

public class Program {

    interface Interface {
        public void printInteger();
    }
    static Interface interfaceInstance = null;

    static void initialize(int val) {
        class Impl implements Interface {
            @Override
            public void printInteger() {
                System.out.println(val);
            }
        }
        interfaceInstance = new Impl();
    }

    public static void main(String[] args) {
        initialize(12345);
        interfaceInstance.printInteger();
    }
}

The interfaceInstance remains in memory after the initialize method returns, but the parameter val does not. The JVM can’t access a local variable outside its scope, so Java makes the subsequent call to printInteger work by copying the value of val to an implicit field of the same name within interfaceInstance. The interfaceInstance is said to have captured the value of the local parameter. If the parameter weren’t final (or effectively final) its value could change, becoming out of sync with the captured value, potentially causing unintuitive behavior.

匿名内部类中的方法可以在生成该类的线程终止后调用。在您的示例中,内部类将在事件分派线程上调用,而不是在创建它的线程中调用。因此,变量的作用域是不同的。所以为了保护这样的变量赋值范围问题,你必须声明它们为final。

由于Jon有实现细节的答案,另一个可能的答案是JVM不想处理已经结束他的激活的写入记录。

考虑这样一个用例,在这个用例中,你的lambdas不是被应用的,而是被存储在某个地方并稍后运行。

我记得在Smalltalk中,当你做这样的修改时,你会得到一个非法的商店。

Java匿名类与Javascript闭包非常相似,但Java以不同的方式实现。(请看安徒生的答案)

所以为了不让Java开发人员对那些有Javascript背景的人的奇怪行为感到困惑。我想这就是为什么他们强迫我们使用final,这不是JVM的限制。

让我们看看下面的Javascript例子:

var add = (function () {
  var counter = 0;

  var func = function () {
    console.log("counter now = " + counter);
    counter += 1; 
  };

  counter = 100; // line 1, this one need to be final in Java

  return func;

})();


add(); // this will print out 100 in Javascript but 0 in Java

在Javascript中,计数器的值是100,因为从头到尾只有一个计数器变量。

但在Java中,如果没有final,它将打印出0,因为在创建内部对象时,0值被复制到内部类对象的隐藏属性中。(这里有两个整数变量,一个在局部方法中,另一个在内部类隐藏属性中)

因此,内部对象创建后的任何更改(如第1行)都不会影响内部对象。所以它会混淆两种不同的结果和行为(Java和Javascript之间)。

我相信这就是为什么,Java决定强制它是最终的,所以数据从开始到结束都是“一致的”。