为什么Java内部类需要"最终"外部实例变量?

3m *_*asr 54 java inner-classes

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");
    }
} );
Run Code Online (Sandbox Code Playgroud)

如果我省略final,我看到错误" 不能引用在不同方法中定义的内部类中的非最终变量jtfContent ".

为什么匿名内部类必须要求外部类实例变量为最终才能访问它?

Poi*_*nty 69

首先,让我们都放松一下,请把那把枪放下.

好.现在语言坚持的原因是它为了让你的内部类函数能够访问他们渴望的局部变量而作弊.运行时会生成本地执行上下文的副本(以及适当的等等),因此它坚持要您创建所有内容final以使其保持诚实.

如果它不这样做,那么在构造对象之后但内部类函数运行之前更改局部变量值的代码可能会令人困惑和奇怪.

这是围绕Java和"闭包"的许多骚动的本质.


注意:开头段落是关于OP原始构成中的一些全文字幕的笑话.

  • @ j2emanue这是一个很好的问题; 我强烈怀疑它是一个浅层副本(因此,如果你有一个对可变对象的`final`引用,那么该对象将通过伪闭包中的代码变为可变).制作深层拷贝通常是一个难题,我认为它不太可能以这种方式工作. (2认同)
  • 这绝对是一个浅薄的副本.Java中没有神奇的"深层复制"机制. (2认同)

Jor*_*ira 22

匿名类中的方法实际上无法访问局部变量和方法参数.相反,当实例化匿名类的对象时,对象方法引用的最终局部变量和方法参数的副本将作为实例变量存储在对象中.匿名类的对象中的方法实际上访问那些隐藏的实例变量.[1]

因此,必须将本地类的方法访问的局部变量和方法参数声明为final,以防止在实例化对象后它们的值发生更改.

[1] http://www.developer.com/java/other/article.php/3300881/The-Essence-of-OOP-using-Java-Anonymous-Classes.htm


Tho*_*sen 8

原因是Java不完全支持所谓的"闭包" - 在这种情况下final没有必要 - 但是通过让编译器生成一些隐藏变量来发现一个技巧,这些变量用于提供您看到的功能.

如果您反汇编生成的字节代码,您可以看到编译器如何执行它,包括包含最终变量副本的奇怪命名的隐藏变量.

这是一种优雅的解决方案,可以在不向后弯曲语言的情况下提供功能.


编辑:对于Java 8,lambdas提供了一种更简洁的方式来完成以前使用匿名类完成的操作.对变量的限制也从"最终"松散到"基本上是最终的" - 你不必将其声明为final,但如果它被视为最终(你可以添加final关键字,你的代码仍然可以编译)可以使用.这是一个非常好的变化.


Bar*_*lom 7

类的定义周围的变量存在于堆栈中,因此当内部类中的代码运行时它们可能已经消失(如果你想知道原因,搜索堆栈和堆).这就是为什么内部类实际上不使用包含方法中的变量,而是使用它们的副本构造.

这意味着如果在构造内部类之后更改contains方法中的变量,则其值不会在内部类中更改,即使您期望它也是如此.为了防止混淆,Java要求它们是最终的,因此您希望不能修改它们.


ans*_*oyt 6

由于Java 8 final修饰符对于外部实例变量是可选的.价值应该是"有效的最终".查看答案最终和有效最终之间的差异.