为什么Java内部类需要“最终的”外部实例variables?
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
, 则会看到错误“ 不能引用在不同方法中定义的内部类中的非最终variablesjtfContent ”。
为什么匿名内部类需要外部类实例variables是最终的才能访问?
首先,让我们放松一下,把枪放下。
好。 现在语言坚持的原因就是它为了让你的内部类函数访问他们想要的局部variables而作弊。 运行时会制作本地执行上下文(如适用)的副本,因此它坚持让所有的东西都是final
这样可以保持诚实。
如果没有这样做,那么在构造对象之后但在内部类函数运行之前更改局部variables值的代码可能会令人困惑和奇怪。
这是围绕Java和“closures”的许多布鲁哈的本质。
注:开头的段落是在OP的原始构成中提及一些全部大写的文字的玩笑。
匿名类中的方法实际上不能访问局部variables和方法参数。 相反,当匿名类的对象被实例化时,对象方法引用的最终局部variables和方法参数的副本将作为实例variables存储在对象中。 匿名类对象中的方法实际上访问这些隐藏的实例variables。 [1]
因此,本地类的方法访问的局部variables和方法参数必须声明为final,以防止在实例化对象后它们的值发生变化。
由于Java 8 final修饰符对于外部实例variables是可选的。 价值应该是“有效的最终”。 查看答案最后的和有效的最终的区别 。
原因是Java并不完全支持所谓的“闭包” – 在这种情况下, final
不是必须的,而是通过让编译器生成一些隐藏的variables来发现一个技巧,这些隐藏的variables用来提供你所看到的function。
如果你反汇编生成的字节码,你可以看到编译器是如何做的,包括含有最终variables拷贝的奇怪命名的隐藏variables。
这是一个优雅的解决scheme,不用弯曲语言就可以实现function。
编辑:对于Java 8的lambdaexpression式给出一个更简洁的方法去做以前用匿名类做的事情。 对variables的限制也从“最终”放宽到“基本上最终” – 你不必声明它是最终的,但是如果它被视为最终的(你可以添加final关键字,你的代码仍然可以编译)可以使用。 这是一个非常好的变化。
围绕类定义的variables存在于栈中,所以当内部类中的代码运行时(如果你想知道为什么,search堆栈和堆),它们可能就不存在了。 这就是为什么内部类实际上不使用包含方法中的variables,而是使用它们的副本构build的。
这意味着,如果在构造内部类之后在包含方法中更改该variables,那么即使您期望它在内部类中的值也不会更改。 为了避免混淆,Java要求它们是最终的,所以你期望不能修改它们。