为什么Java允许在匿名内部类中重新分配类级别的变量,而局部变量不允许这样做

时间:2018-12-09 13:39:50

标签: java lambda anonymous-inner-class

这个问题类似于Lambdas: local variables need final, instance variables don't,但是唯一的区别是,即使没有lambda表达式,这个问题也有效,即即使在Java7上也有效。

这是下面的代码段。

公共类MyClass {

Integer globalInteger = new Integer(1);

public void someMethod() {

    Integer localInt = new Integer(2);

    Runnable runnable = new Runnable() {

        @Override
        public void run() {

            globalInteger = new Integer(11);//no error
            localInt =  new Integer(22);//error here

        }
    };      
}

}

我可以重新分配globalInteger新值,但不能重新分配localInteger。为什么会有这种差异?

4 个答案:

答案 0 :(得分:3)

要了解为什么允许更改非局部变量,我们首先需要了解为什么不允许局部变量。那是因为局部变量存储在堆栈中(不是实例(或静态)变量)。

堆栈变量的问题是,一旦包含方法返回,它们就会消失。但是,您的匿名类实例的寿命可能会更长。因此,如果访问本地变量是幼稚的实现,则在返回的方法之后在内部类内部使用本地变量将访问堆栈框架中不再存在的变量。这将导致崩溃,异常或不确定的行为,具体取决于确切的实现。由于这显然很糟糕,因此可以通过复制来实现对局部变量的访问。也就是说,该类使用的所有局部变量(包括特殊变量this)都将复制到匿名对象中。因此,当内部类的方法访问局部变量x时,实际上并没有访问该局部变量。它正在访问存储在对象中的副本。

但是,如果在创建对象后更改了局部变量或对象的方法更改了变量,将会发生什么?好吧,前者将导致局部变量更改,但对象中的副本不会更改,而后者将更改副本,但对象中的副本不会更改。因此,无论哪种方式,变量的两个版本都将不再相同,这对于任何不知道正在进行复制的程序员来说都是非常违反直觉的。因此,为避免出现此问题,仅允许在从未更改局部变量值的情况下访问它们。

不需要复制实例变量,因为它们不会消失,直到它们的包含对象被垃圾回收为止(静态变量永远不会消失)-因为匿名对象将包含对外部this的引用,在匿名对象也被垃圾回收之前,这种情况不会发生。因此,由于未复制它们,因此修改它们不会引起任何问题,也没有理由禁止这样做。

答案 1 :(得分:1)

因为JVM没有机器指令来分配位于与当前堆栈帧不同的任何堆栈帧的变量。

答案 2 :(得分:0)

因为lambda函数不属于该类。

考虑以下代码(您的一些小改动):

public Runnable func() {
    Integer localInt = new Integer(2);
    Runnable runnable = new Runnable() {
        @Override
        public void run() {
            globalInteger = new Integer(11);//no error
            localInt =  new Integer(22);//error here
        }
    };
    return runnable;
}

//Somewhere in the code:
Runnable r = func();
r.run(); // At this point localInt is not defined.

答案 3 :(得分:0)

编译器会告诉您以下错误:内部类中的变量必须为final或有效为final。这是由于Java的8闭包以及JVM如何捕获引用。限制是,在lambda主体中捕获的引用必须是最终引用(不能重新分配),并且编译器需要确保它不引用局部变量的副本。

因此,如果您访问实例变量,则您的lambda实际上是在引用周围类的this实例,该实例是有效的final(不变的引用)。此外,如果使用包装器类或数组,则编译器错误也将消失。