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

2022-08-31 04:44:31
  1. a这里只能是最终的。为什么?如何在不将其保留为私有成员的情况下重新分配方法?aonClick()

    private void f(Button b, final int a){
        b.addClickHandler(new ClickHandler() {
    
            @Override
            public void onClick(ClickEvent event) {
                int b = a*5;
    
            }
        });
    }
    
  2. 如何返回点击时的 ?我的意思是5 * a

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

答案 1

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


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

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

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

使变量最终消除所有这些可能性 - 由于值根本无法更改,因此您无需担心此类更改是否可见。允许方法和匿名内部类看到彼此更改的唯一方法是使用某种描述的可变类型。这可能是封闭类本身,数组,可变包装器类型...任何类似的东西。基本上,这有点像在一种方法和另一种方法之间进行通信:对一种方法的参数所做的更改不会被其调用者看到,但对参数引用的对象所做的更改会被看到。

如果您对Java和C#闭包之间的更详细的比较感兴趣,我有一篇文章可以进一步介绍它。我想在这个答案中专注于Java方面:)


答案 2

有一个技巧允许匿名类更新外部范围中的数据。

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

// ...

推荐