为什么匿名类可以访问封闭类的非final成员?

16

我们知道只有最终的本地变量才能在匿名类中访问,这里有一个很好的原因: 为什么只有最终变量可以在匿名类中访问?.

然而,我发现如果变量是封闭类的成员字段,则匿名类仍然可以访问非最终变量: 如何从匿名类内部访问封闭类实例变量?

我感到困惑。我们确保只有最终本地变量才能在匿名类中访问,因为我们不希望变量在匿名类和本地函数之间不同步。同样的原因也应该适用于在匿名类中尝试访问非最终封闭类成员的情况。

为什么这不是一个问题呢?


如果我们尝试在匿名类中访问非 final 实例变量,同样的原因也适用。这被称为从它的引用访问对象的字段。为什么对象的起始点或声明的类会影响到这一点呢? - S.D.
@user2499800:你看到我的回答了吗?你在这方面取得了什么进展?如果你回复一下,我会很感激... :) - Yash Sampat
4个回答

8
在本地变量的情况下,匿名类实例接收的是变量的一个“副本”。因此,在变量在匿名类中使用之前,必须将其声明为final,以防其值稍后发生更改。
对于封闭类的成员字段,没有副本。相反,匿名类获得对封闭类的引用,从而访问外部类的任何/所有成员字段和方法。因此,即使字段的值发生更改,匿名类也会反映这种变化,因为它是同一个引用。
“我感到困惑。我们确保只有一个final的局部变量可以在匿名类中访问,因为我们不希望变量在匿名类和本地函数之间失去同步。如果我们尝试在匿名类中访问非final的封闭类成员,应该适用相同的原因。”
正如您所看到的那样,事实并非如此。复制仅针对本地变量,而不是封闭类的成员字段。原因是匿名类包含对封闭类的隐式引用,通过该引用,它可以访问外部类的任何/所有成员字段和方法。
引用:
1. 为什么无法在内部类中使用非final的“局部”变量,而可以使用封闭类的非final字段?

你可以通过解释为什么可以使用引用而不是副本来改进你的答案,尽管这可能很明显。 - theMfromA
相反,匿名类实例只会获得对封闭实例的引用,而不是实际成员字段的引用。 - newacct

2
非静态/内部类具有对其封闭实例的引用。因此,它们可以隐式地引用实例变量和方法。
如果是参数,则即使封闭类不知道它,因为它只能从定义此变量的方法中访问。
如Y.S.所指出的那样:
在局部变量的情况下,匿名类实例得到的是变量的副本。

“非静态内部类”是一个自我重复的说法。如果类是静态的并且在另一个类中声明,它们被称为“静态成员类”。如果它们不是静态的,则称为“内部类”。 - Erwin Bolwidt
@ErwinBolwidt:是的,我忘记了斜杠。我实际上想写的是非静态/内部类。谢谢你的提示。 - B. Kemmer

1
考虑以下例子。
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();
    }
}

在Java 1.8或以上版本中,这不会产生任何错误。但在之前的版本中,它会生成一个错误,要求您明确声明内部类中访问的局部变量为final。因为编译器所做的就是保留内部类访问的局部变量的副本,以便即使方法/块结束并且局部变量超出范围,该副本仍将存在。它要求我们声明它为final,因为如果变量在程序后期动态更改其值,则由编译器创建的副本不会更改为其新值,并且可能会导致内部类中的问题,从而无法产生预期的输出。因此,它建议我们明确声明它为final。
但在Java 1.8中,它不会生成错误,因为编译器隐式地将访问的局部变量声明为final。 Java文档中如下所述:
“匿名类不能访问其封闭作用域中未声明为final或有效final的局部变量。”
让我解释一下有效final的含义。考虑上面程序的以下修改版本。
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;
    }
}

即使在Java 1.8中,这也会产生错误。这是因为aLocal在程序中被动态赋值。这意味着编译器无法将变量视为有效的final。根据我所理解的,编译器将那些没有被动态更改的变量声明为final。这被称为变量是有效的final
因此,建议您明确声明由局部内部类或匿名类访问的局部变量为final,以避免任何错误。

我是初学者,你能解释一下第一段代码吗? 我不明白Innersuper mouter()是什么意思,因为你没有用到它。 - chandu_reddim
1
mOuterOuter类的一个方法,它返回InnerSuper类的一个实例。该方法在主方法中执行。如果这是您需要澄清的内容。 - Missaka Iddamalgoda
先生,如果我没错的话,Inner是一个本地内部类,但我得知我们无法将本地内部类传递给其他人。但是在这里,您能够做到,这是怎么回事呢?或者我学错了,“本地内部类不能被传递或返回”。我相信这是因为本地内部类属于块(方法、if或for)。请澄清一下,先生。是的,先生。我想问一下mouter()是什么。关于mouter的内容,我已经明确了什么是mOuter。 - chandu_reddim
无法返回或传递本地内部类的原因是,局部作用域之外的其他类没有访问类定义的权限,使其对作用域之外的其他类无用。因此,Java编译器被编写成不允许返回本地内部/匿名类。但在这种情况下,我返回的是InnerSuper Class的子类实例,在这里其他类可以访问该类定义。希望您了解如何使用Super/Parent Class与Child Class实例。 - Missaka Iddamalgoda
是的,先生。谢谢您,先生。我详细地阅读了每一行。我明白了,先生。 - chandu_reddim

1

实际上,一个本地变量(例如x)在匿名实例之外的方法调用期间存在。它的对象引用存储在调用堆栈中;x == 包含对象引用的堆栈地址。匿名实例内部的x是一个副本,因为它的生命周期不同,更长或更短。

由于现在有两个变量,因此决定不允许对x进行赋值(实现起来很奇怪),并要求该变量为“有效最终变量”。

可以访问外部成员,因为匿名实例是一个包含OuterClass.this引用的内部类。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接