【问题标题】:Why Java inner classes require "final" outer instance variables? [duplicate]为什么 Java 内部类需要“最终”外部实例变量? [复制]
【发布时间】:2011-04-24 01:04:20
【问题描述】:
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,我会看到错误“Cannot reference an non-final variable jtfContent inside an internal class defined in a different method”。

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

【问题讨论】:

  • 我注意到这个问题比它声称是重复的问题更早。
  • @Raedwald 根据各自的元讨论,问题的时间并不重要:Should I vote to close a duplicate question, even though it's much newer... - “如果新问题是更好的问题或有更好的答案,则投票结束旧的与新的重复..."
  • 有人能解释一下为什么在 Java 1.8 中这段代码会通过编译吗?
  • 因为在 Java 8 中,它允许 final 或 实际上是 final 的变量在这里通过编译器。
  • @bruno 这不应该是“局部”变量或方法参数,而不是外部“实例”变量吗?

标签: java inner-classes


【解决方案1】:

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

好的。现在语言坚持这样做的原因是它作弊是为了让你的内部类函数访问他们渴望的局部变量。运行时会制作本地执行上下文的副本(以及适当的其他内容),因此它坚持让您制作所有内容final,以便保持诚实。

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

这是围绕 Java 和“闭包”的许多争论的本质。


注意:开头段落是一个笑话,引用了 OP 原始作品中的一些全大写文本。

【讨论】:

  • @j2emanue 这是个好问题;我强烈怀疑这是一个浅拷贝(因此,如果您有一个对可变对象的final 引用,则该对象将通过伪闭包中的代码可变)。制作深拷贝通常是一个难题,我认为它不太可能以这种方式工作。
  • 绝对是浅拷贝。 Java 中没有神奇的“深拷贝”机制。
  • 是的,原始类型像在 java 中一样“按值”复制,其他一切都是浅拷贝。多亏了垃圾收集器,这种浅拷贝很容易实现。
  • @getsadzeg 我所做的就是使用非最终局部变量执行我需要执行的任何工作,然后将任何结果复制到仅存在的其他最终变量中以使“假闭包”工作.它非常丑陋,但如果我担心 Java 中丑陋的东西,我会是一个非常沮丧的人。
  • @Pointy 当你想初始化一个 final 字段时也是一样的,例如try { MY_CONSTANT = foo(); } catch(SomeException ex) { MY_CONSTANT = foo(); } 不起作用;该解决方案与您要捕获的变量相同。所以它实际上与闭包无关,它只是关于明确分配的严格性。
【解决方案2】:

匿名类中的方法 真的无法访问本地 变量和方法参数。 相反,当一个对象 匿名类被实例化, 最终本地副本 变量和方法参数 由对象的方法引用 作为实例变量存储在 物体。对象中的方法 匿名类的真正访问 那些隐藏的实例变量。 [1]

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

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

【讨论】:

  • +1 明确指出限制(直到 java 8)是 w.r.t 局部变量和方法参数,而不是实例变量。
【解决方案3】:

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

这意味着如果你在构造内部类之后改变包含方法中的变量,它的值不会在内部类中改变,即使你期望它会改变。为防止混淆,Java 要求它们是最终的,因此您不能修改它们。

【讨论】:

  • 我认为这是最好的答案。局部变量存在于堆栈中。但是类生活在堆上。所以JVM通过它的构造方法复制这个变量。但是如果变量改变了,这两个值就不同了。
【解决方案4】:

原因是 Java 不完全支持所谓的“闭包”——在这种情况下,final 不是必需的——而是通过让编译器生成一些隐藏变量来找到一个技巧,这些变量用于给出您看到的功能。

如果你反汇编生成的字节码,你可以看到编译器是怎么做的,包括名字奇怪的隐藏变量,其中包含最终变量的副本。

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


编辑:对于 Java 8,lambdas 提供了一种更简洁的方式来完成以前使用匿名类所做的事情。对变量的限制也从“final”放松到“essentially final”——你不必将它声明为final,但如果它被处理就像它是final(你可以添加final关键字和您的代码仍然可以编译)它可以使用。这是一个非常好的变化。

【讨论】:

  • 我可能会说“不优雅”而不是“优雅”,但它就是这样。我想它在 java8 中要好一些... :)
  • 优雅的语法。而且 - 一如既往 - 随意写一个更好的答案。
【解决方案5】:

从 Java 8 开始,final 修饰符对于外部实例变量是可选的。价值应该是“有效的最终”。见答案Difference between final and effectively final

【讨论】:

    猜你喜欢
    • 2014-06-20
    • 1970-01-01
    • 2020-04-07
    • 2015-02-24
    • 2011-11-17
    • 2017-11-07
    • 1970-01-01
    • 2017-12-22
    • 1970-01-01
    相关资源
    最近更新 更多