一尘不染

为什么在匿名类中只能访问final变量?

java 匿名类 final

  1. 在这里只能是最终的。为什么?如何在onClick()不保留为私有成员的情况下重新分配方法?
private void f(Button b, final int a){
    b.addClickHandler(new ClickHandler() {

        @Override
        public void onClick(ClickEvent event) {
            int b = a*5;

        }
    });
}
  1. 单击该如何返回?
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
        }
    });
}

阅读 665

收藏
2020-01-10

共1个答案

一尘不染

如注释中所述,其中一些在Java 8中变得无关紧要,在Java 8中final可以隐式使用。但是,只能在匿名内部类或lambda表达式中使用有效的最终变量。

这基本上是由于Java管理闭包的方式。

创建匿名内部类的实例时,该类中使用的任何变量都将通过自动生成的构造函数复制其值。这样避免了编译器不得不自动生成各种额外的类型来保存“局部变量”的逻辑状态,例如C#编译器确实…(当C#在匿名函数中捕获变量时,它实际上捕获了变量-闭包可以通过该方法的主体看到的方式来更新变量,反之亦然。)

由于该值已被复制到匿名内部类的实例中,因此如果变量的其余部分可以被该方法的其余部分修改,则看起来会很奇怪-你可能会发现代码似乎正在使用过期的变量(因为这实际上是将要发生的事情……你将使用在不同时间拍摄的副本)。同样,如果你可以在匿名内部类中进行更改,则开发人员可能希望这些更改在封闭方法的正文中可见。

将变量设为final可以消除所有这些可能性-由于根本无法更改该值,因此你无需担心此类更改是否可见。允许方法和匿名内部类看到彼此更改的唯一方法是使用某种描述的可变类型。这可能是封闭的类本身,一个数组,一个可变的包装器类型……诸如此类。基本上,这有点像一种方法与另一种方法之间的通信:调用者看不到对一个方法的参数所做的更改,但可以看到对参数所引用的对象所做的更改。

如果你想对Java和C#闭包之间的更详细的比较感兴趣,请参阅我的文章。我想在这个答案中专注于Java方面:)

有一个技巧可以允许匿名类更新外部作用域中的数据。

private void f(Button b, final int a) {
    final int[] res = new int[1];
    b.addClickHandler(new ClickHandler() {
        @Override
        public void onClick(ClickEvent event) {
            res[0] = a * 5;
        }
    });

    // But at this point handler is most likely not executed yet!
    // How should we now res[0] is ready?
}

但是,由于同步问题,此技巧不是很好。如果稍后调用处理程序,则需要1)如果从其他线程调用了处理程序,则同步对res的访问2)需要具有某种标志或指示,表明res已更新

但是,如果立即在同一线程中调用匿名类,则此技巧行得通。喜欢:

// ...

final int[] res = new int[1];
Runnable r = new Runnable() { public void run() { res[0] = 123; } };
r.run();
System.out.println(res[0]);

// ...
2020-01-10