匿名内部类中的Java本地变量可见性 – 为什么需要’final’关键字?

我不明白为什么我不能总是从’listener’或’handler’中访问变量。

这是我的代码:

Button btnDownload = new Button(myparent, SWT.NONE); btnDownload.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { btnDownload.setEnabled(false); // I CAN'T } }); 

唯一的方法是使用final关键字声明它:

 final Button btnDownload = new Button(myparent, SWT.NONE); 

为什么我需要声明变量final以获取事件内的访问权限?

您的SelectionAdapter是一个匿名的内部类,我认为这清楚了:

本地类可以绝对引用实例变量。 他们无法引用非最终局部变量的原因是因为本地类实例可以在方法返回后保留在内存中。 当方法返回时,局部变量超出范围,因此需要它们的副本。 如果变量不是最终的,那么方法中变量的副本可能会改变,而本地类中的副本则没有,因此它们将不同步。

匿名内部类需要最终变量,因为它们在Java中实现的方式。 匿名内部类(AIC)通过创建私有实例字段来使用局部变量,该字段包含局部变量值的副本。 内部类实际上并不使用局部变量,而是使用副本。 在这一点上应该相当明显,如果原始值或复制值发生变化,可能会发生“Bad Thing”™; 会有一些意外的数据同步问题。 为了防止出现这种问题,Java要求您将AIC使用的局部变量标记为final(即不可更改)。 这可以保证内部类的局部变量副本始终与实际值匹配。

我相信Tom说的是,如果你可以在匿名类中使用局部变量,那么它应该在以下代码段中启用哪个按钮。

  Button btnDownload = new Button(myparent, SWT.NONE); btnDownload.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { btnDownload.setEnabled(false); // I CAN'T } }); btnDownload = new Button(somethingelse , SWT.NONE); btnDownload = null ; 

java的制作者希望通过要求匿名类中使用的局部变量是最终的来避免这种争论。

它可能不是最佳的设计选择。 在Java 8中,与lambda表达式一起,希望final这个要求将被删除。

目标是禁止从匿名类分配本地变量。 但这并不要求将局部变量标记为final。

 void f() Object var = ...; new Anon(){ ... print(var); // reading is ok var = x; // error, can't write to var [1] } 

编译器实际上创建了var的副本并将其保存在匿名类中。 匿名类仅在以后访问该副本。 上面的代码实际上转换为

 void f() Object var = ...; new Anon$1(var); class Anon$1 { final Object $var; Anon$1(Object var){ this.$var=var; } ... print($var); $var = x; // error, naturally [2] } 

如您所见,没有技术理由要求var是最终的。 所有编译器必须这样做,当遇到[2]时,知道$var$var的合成字段,报告错误“本地变量var不能由匿名类分配”(而不是“$ var是final,不能是分配给”)

语言设计者选择通过在局部变量上使用final关键字来惹恼我们; 我不记得理由; 一般来说,如果需要清晰度,Java并不害怕冗长。

这篇文章解释了原因

Java方法变量和匿名类