繁体   English   中英

为什么非最终的“局部”变量不能在内部类中使用,而是封闭类的非最终字段可以?

[英]Why a non-final “local” variable cannot be used inside an inner class, and instead a non-final field of the enclosing class can?

关于编译器错误的堆栈溢出有一些主题Cannot refer to a non-final variable message inside an inner class defined in a different method ,并且解决方案是“将其声明为最终并且您已完成”,但是这个理论问题我想检查一下这段代码无法编译的逻辑原因是什么:

private void updateStatus(String message) {
    Runnable doUpdateStatus = new Runnable() {
         public void run() {
             /* do something with message */
         }
    }
    /* do something with doUpdateStatus, like SwingUtilities.invokeLater() */
}

(解决方案:将message声明为最终message ),而这个message是:

private String enclosingClassField;
private void updateStatus() {
    Runnable doUpdateStatus = new Runnable() {
         public void run() {
             /* do something with enclosingClassField */
         }
    }
    /* do something with doUpdateStatus, like SwingUtilities.invokeLater() */
}

我真的很困惑。 enclosingClassField不是final,它可以每次多次更改,而updateStatus的不良message参数只能在其方法体内更改,而是由编译器指责;)

即使是编译器错误也会误导我。 Cannot refer to a non-final variable message inside an inner class defined in a different method与什么不同? message是否与内部类在同一方法中定义? 是不是enclosingClassField而是在方法之外定义? 呃... ...

有人能指出我对此事的正确解释吗? 谢谢。

区别在于本地(方法)变量与类成员变量之间的区别。 成员变量在封闭对象的生命周期中存在,因此可以由内部类实例引用。 但是,局部变量仅在方法调用期间存在,并且由编译器以不同方式处理,因为它的隐式副本是作为内部类的成员生成的。 在不声明局部变量fi​​nal的情况下,可以更改它,导致细微的错误,因为内部类仍然引用该变量的原始值。

更新: Java专家的新闻稿#25更详细地讨论了这一点。

即使是编译器错误也会误导我。 Cannot refer to a non-final variable message inside an inner class defined in a different method与什么不同?

从内心阶级的run方法我相信。

原因是Java不支持闭包 没有JVM命令可以从方法外部访问局部变量,而类的字段可以从任何地方轻松访问。

因此,当您在内部类中使用final局部变量时,编译器实际上将该变量的值传递给内部类的构造函数。 显然,它不适用于非final变量,因为它们的值可以在构造内部类之后发生变化。

包含类的字段没有这个问题,因为编译器隐式地将对包含类的引用传递给内部类的构造函数,因此当您访问任何其他类的字段时,您可以以正常方式访问其字段。

三种类型的东西:实例变量,局部变量和对象:

■ Instance variables and objects live on the heap.
■ Local variables live on the stack.

内部类对象不能使用定义本地内部类的方法的局部变量。

因为使用方法的局部变量是方法的局部变量保留在堆栈上并且一旦方法结束就丢失。

但即使在方法结束后,本地内部类对象仍可能在堆上存活。 方法本地内部类仍然可以使用标记为final的局部变量。

最终变量JVM将它们作为常量,因为它们在启动后不会更改。 当内部类尝试访问它们时, 编译器会将该变量的副本创建到堆中,并在内部类中创建一个合成字段,因此即使方法执行结束,也可以访问它,因为内部类具有自己的副本

合成字段是在源代码中实际存在的,但编译器在某些内部类中创建这些字段以使这些字段可访问。

您使用的值必须是final,但可以更改最终引用的非final字段。 注意: this是隐含的最终参考。 你无法改变它。

private String enclosingClassField;
private void updateStatus() {
    final MutableClass ms = new MutableClass(1, 2);
    Runnable doUpdateStatus = new Runnable() {
         public void run() {
             // you can use `EnclosingClass.this` because its is always final
             EnclosingClass.this.enclosingClassField = "";
             // shorthand for the previous line.
             enclosingClassField = "";
             // you cannot change `ms`, but you can change its mutable fields.
             ms.x = 3;
         }
    }
    /* do something with doUpdateStatus, like SwingUtilities.invokeLater() */
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM