Java 为什么匿名类可以访问封闭类的非最终类成员

Java 为什么匿名类可以访问封闭类的非最终类成员,java,closures,final,anonymous-class,Java,Closures,Final,Anonymous Class,我们知道在匿名类中只能访问最终的局部变量,这里有一个很好的理由: 但是,我发现匿名类仍然可以访问非final变量,如果该变量是封闭类的成员字段: 我很困惑。我们确保在匿名类中只能访问最终的局部变量,因为我们不希望该变量在匿名类和局部函数之间不同步。如果我们试图访问匿名类中的非最终封闭类成员,同样的原因也应该适用于这种情况 为什么这不是一个问题 对于局部变量,匿名类实例接收的是该变量的副本。因此,在匿名类中使用局部变量之前,必须将其设置为final,以便以后不会更改其值 对于封闭类的成员字段,没有

我们知道在匿名类中只能访问最终的局部变量,这里有一个很好的理由:

但是,我发现匿名类仍然可以访问非final变量,如果该变量是封闭类的成员字段:

我很困惑。我们确保在匿名类中只能访问最终的局部变量,因为我们不希望该变量在匿名类和局部函数之间不同步。如果我们试图访问匿名类中的非最终封闭类成员,同样的原因也应该适用于这种情况


为什么这不是一个问题

对于局部变量,匿名类实例接收的是该变量的副本。因此,在匿名类中使用局部变量之前,必须将其设置为
final
,以便以后不会更改其值

对于封闭类的成员字段,没有副本。相反,匿名类获取对封闭类的引用,从而访问外部类的任何/所有成员字段和方法。因此,即使字段的值更改,更改也会反映在匿名类中,因为它是相同的引用

我很困惑。我们确保只有一个最终的局部变量可以 在匿名类中访问,因为我们不希望该变量 匿名类和本地函数之间应不同步。这个 如果我们试图访问非最终版本,同样的理由也应适用于该案例 在匿名类中封闭类成员

如你所见,情况并非如此。复制只针对局部变量,而不针对封闭类的成员字段原因当然是,匿名类拥有对封闭类的隐式引用,通过该引用,它可以访问外部类的任何/所有成员字段和方法。

引用以下链接:

成员变量在封闭对象的生存期内存在,因此内部类实例可以引用它。但是,局部变量仅在方法调用期间存在,编译器处理方式不同,因为它的隐式副本是作为内部类的成员生成的。在不声明局部变量final的情况下,可以对其进行更改,从而导致细微的错误,因为内部类仍然引用该变量的原始值

参考文献:


1。

非静态/内部类有一个对封闭实例的引用。因此,它们可以隐式引用实例变量和方法

如果它是一个参数,即使是封闭类也不知道它的任何信息,因为它只能从定义此变量的方法访问

正如Y.S.已经指出的:


对于局部变量,该变量的副本是匿名类实例获得的


反过来说,匿名实例之外的局部变量,比如
x
,只要方法调用有效,它就会存在。其对象引用存储在调用堆栈上;x==包含对象引用的堆栈上的地址。匿名实例中的
x
是一个副本,因为它的生存期不同、更长甚至更短

由于现在有两个变量,因此决定不允许分配到
x
(执行起来很奇怪),并要求变量为“有效的最终变量”


访问外部成员是因为匿名实例属于内部类,也包含外部类。请参阅此引用。

考虑以下示例

class InnerSuper{
    void mInner(){}
}
class Outer{
    int aOuter=10;
    InnerSuper mOuter(){
        int aLocal=3999;
        class Inner extends InnerSuper{
            int aInner=20;
            void mInner(){
                System.out.println("a Inner : "+aInner);
                System.out.println("a local : "+aLocal);
            }
        }
        Inner iob=new Inner(); 
        return iob;
    }
}

class Demo{
    public static void main(String args[]){
        Outer ob=new Outer();
        InnerSuper iob=ob.mOuter(); 
        iob.mInner();
    }
}
这不会在Java1.8或更高版本中产生任何错误。但在以前的版本中,这会生成一个错误,要求您将内部类中访问的局部变量显式声明为final。 因为编译器所做的是保留内部类访问的局部变量的副本,这样即使方法/块结束且局部变量超出范围,副本也会存在。它要求我们声明它final,因为如果在声明本地内部类或匿名类之后,变量在程序中动态更改其值,编译器创建的副本不会更改为其新值,并且可能会由于不生成预期输出而在内部类中引起问题。因此,它建议我们明确宣布它为最终版本

但在Java1.8中,它不会生成错误,因为编译器隐式地声明访问的局部变量为final。 在Java文档中说明如下

匿名类无法访问其封闭类中的局部变量 未声明为最终或有效最终的范围

让我解释一下“有效最终”是什么意思。考虑下面修改的版本

class Outer{
    int aOuter=10;
    InnerSuper mOuter(){
        int aLocal=3999;
        class Inner extends InnerSuper{
            int aInner=20;
            void mInner(){
                System.out.println("a Inner : "+aInner);
                System.out.println("a local : "+aLocal);
            }
        }
        aLocal=4000;
        Inner iob=new Inner(); 
        return iob;
    }
}
即使在Java1.8中,这也会产生错误。这是因为aLocal是在程序中动态分配的。这意味着编译器不能有效地将变量视为最终变量。据我所知,编译器将未动态更改的变量声明为final。这称为变量is有效地为final


因此,建议您显式声明本地内部类或匿名类访问的本地变量,以避免任何错误

您可以通过解释为什么可以使用引用而不是副本来改进您的答案。虽然这可能很明显。“相反,匿名类实例获得了对实际成员字段的引用。”不。它们只对封闭实例进行了引用。
如果我们尝试访问匿名类中的非最终实例变量,同样的原因也可能适用于这种情况。
。。。。那叫acces