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

时间:2011-04-27 09:17:16

标签: java

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

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

(解决方案:声明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不是最终的,它可以每次都改变很多次,而message的穷updateStatus参数只能在其方法体内改变,而是由编译器指责;)< / p>

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

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

4 个答案:

答案 0 :(得分:32)

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

更新: The Java Specialists' Newsletter #25更详细地讨论了这一点。

  

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

我相信内部的'run方法。

答案 1 :(得分:16)

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

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

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

答案 2 :(得分:9)

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

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

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

因为使用方法的局部变量是方法的局部变量保留在堆栈上并在方法结束后立即丢失。

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

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

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

答案 3 :(得分:4)

您使用的值必须是最终值,但最终引用的非最终字段可以更改。注意: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() */
}