final JTextField jtfContent = new JTextField();
btnOK.addActionListener(new java.awt.event.ActionListener(){
public void actionPerformed(java.awt.event.ActionEvent event){
jtfContent.setText("I am OK");
}
} );
如果我省略final
,我会看到错误“不能引用在不同方法中定义的内部类中的非最终变量jtfContent ”。
为什么匿名内部类必须要求外部类实例变量为final才能访问它?
答案 0 :(得分:65)
首先,让我们都放松一下,请把那把枪放下来。
行。现在语言坚持的原因是它为了让你的内部类函数能够访问他们渴望的局部变量而作弊。运行时会生成本地执行上下文的副本(以及适当的等等),因此它会坚持让您创建所有内容final
,以便它可以保持诚实。
如果它没有这样做,那么在构造对象之后改变局部变量值的代码但在内部类函数运行之前可能会让人感到困惑和奇怪。
这是围绕Java和“封闭”的许多骚动的本质。
注意:开头段落是对OP原始构成中的一些全文字幕的引用的笑话。
答案 1 :(得分:21)
匿名类中的方法 真的没有访问本地 变量和方法参数。 相反,当一个对象的时候 匿名类被实例化, 最终本地的副本 变量和方法参数 由对象的方法引用 作为实例变量存储在 物体。对象中的方法 匿名类真正访问 那些隐藏的实例变量。 [1]
因此,必须将本地类的方法访问的局部变量和方法参数声明为final,以防止在实例化对象后更改它们的值。
答案 2 :(得分:8)
原因是Java不完全支持所谓的“闭包” - 在这种情况下final
不是必需的 - 而是通过让编译器生成一些隐藏的变量来找到一个技巧提供您看到的功能。
如果你反汇编生成的字节代码,你可以看到编译器是如何做到的,包括奇怪命名的包含最终变量副本的隐藏变量。
这是一种优雅的解决方案,可以提供功能而不会向后弯曲语言。
编辑:对于Java 8,lambdas提供了一种更简洁的方式来完成以前使用匿名类完成的操作。变量的限制也从“最终”松散到“基本上最终” - 您不必将其声明为最终,但如果处理就像它是最终的那样(您可以添加最终关键字和你的代码仍然可以编译)它可以使用。这是一个非常好的变化。
答案 3 :(得分:7)
类的定义周围的变量存在于堆栈中,因此当内部类中的代码运行时(如果您想知道原因,搜索堆栈和堆),它们可能已经消失了。这就是为什么内部类实际上不使用包含方法中的变量,而是使用它们的副本构造。
这意味着如果在构造内部类之后更改contains方法中的变量,则它的值在内部类中不会更改,即使您期望它也是如此。为了防止混淆,Java要求它们是最终的,所以你希望它们不能修改它们。
答案 4 :(得分:6)
由于Java 8 final 修饰符对于外部实例变量是可选的。价值应该是有效的最终结果。请参阅答案Difference between final and effectively final。