为什么嵌套的子类可以访问其父类的私有成员,但是孙子们不能?

可能类似于这个问题, 为什么外部Java类可以访问内部类私有成员? 或者在子类中使用super关键字访问超类专用字段 。

但是有一些不同之处:子类可以访问父类(也就是最近的父类)的私有成员。

鉴于下面的示例代码:

public class T { private int t; class T1 { private int t1; public void test() { System.out.println(t); } } class T2 extends T1 { private int t2; public void test() { System.out.println(t); System.out.println(super.t1); System.out.println(this.t2); } } class T3 extends T2 { public void test() { System.out.println(t); System.out.println(super.t1); // NG: t1 Compile error! Why? System.out.println(super.t2); // OK: t2 OK } } } 

聪明的例子! 但实际上这是一个有点无聊的解释 – 没有可见性问题,你根本没有办法直接从T3引用t1 ,因为super.super是不允许的 。

T2不能直接访问自己的t1字段,因为它是私有的(并且子类不会inheritance它们的父私有字段),但是super实际上是T1一个实例,因为它在同一个类T2中可以引用私有字段superT3没有任何机制直接解决祖父级T1的私有领域。

T3这两种编译都很好,这表明T3可以访问其祖父母的private字段:

 System.out.println(((T1)this).t1); System.out.println(new T1().t1); 

相反,这不能在T2T3编译:

 System.out.println(t1); 

如果super.super被允许,你可以从T3做到这一点:

 System.out.println(super.super.t1); 

如果我定义了3个类,具有保护字段t1B ABCA将从BinheritanceACC可以通过调用super.t1来引用A s t1 ,因为在这里可见。 从逻辑上讲,不应该同样适用于内部类inheritance,即使该字段是私有的,因为这些私有成员应该是可见的,因为在同一个类中?

(为了简单起见,我将坚持使用OP的T1T2T3类名)

如果t1protected ,就没有问题了 – T3可以像任何子类一样直接引用t1字段。 这个问题出现在private因为一个阶级没有意识到其父类的private领域,因此不能直接引用它们,即使在实践中它们是可见的。 这就是为什么你必须使用T2 super.t1 ,以便甚至可以参考相关字段。

即使就T3而言,它也没有t1字段,它可以通过在同一个外部类中进入T1private字段。 既然是这样的话,所有你需要做的就是将this转换为T1并且你有办法引用私有字段。 T2super.t1调用(实质上)是将this转换为T1让我们引用它的字段。

Sythetic Accessor方法

从技术上讲,在JVM级别上, 不能访问另一个类的private成员 – 既不是封闭类( Tt ),也不是父类( T2.t2 )的private成员。 在你的代码中它看起来像你可以,因为编译器为你在访问的类中生成synthetic的访问器方法。 在T3类中使用正确的forms((T1) this).t1修复无效的引用super.t1发生同样的情况。

借助这种编译器生成的synthetic存取方法,您可以在一般情况下访问嵌套在外层(顶层) T类中的任何类的private成员,例如从T1可以使用new T2().t2 。 请注意,这也适用于private static成员。

JDK 1.1版中引入了synthetic属性,以支持嵌套类,这是当时java中的一种新语言function。 从那时起, JLS明确允许相互访问顶级类中的所有成员,包括private成员。

但为了向后兼容,编译器解开嵌套类(例如T$T1T$T2T$T3 ),并将private成员访问转换为生成的synthetic访问器方法的调用 (因此这些方法需要私有包 ,即默认 ,能见度):

 class T { private int t; T() { // generated super(); // new Object() } static synthetic int access$t(T t) { // generated return tt; } } class T$T1 { private int t1; final synthetic T t; // generated T$T1(T t) { // generated this.t = t; super(); // new Object() } static synthetic int access$t1(T$T1 t$t1) { // generated return t$t1.t1; } } class T$T2 extends T$T1 { private int t2; { System.out.println(T.access$t((T) this.t)); // t System.out.println(T$T1.access$t1((T$T1) this)); // super.t1 System.out.println(this.t2); } final synthetic T t; // generated T$T2(T t) { // generated this.t = t; super(this.t); // new T1(t) } static synthetic int access$t2(T$T2 t$t2) { // generated return t$t2.t2; } } class T$T3 extends T$T2 { { System.out.println(T.access$t((T) this.t)); // t System.out.println(T$T1.access$t1((T$T1) this)); // ((T1) this).t1 System.out.println(T$T2.access$t2((T$T2) this)); // super.t2 } final synthetic T t; // generated T$T3(T t) { // generated this.t = t; super(this.t); // new T2(t) } } 

注意:你不能直接引用synthetic成员,所以在源代码中你不能使用例如int i = T.access$t(new T()); 你自己。

非常好的发现! 我想,我们都假设你的代码示例应该编译。

不幸的是,情况并非如此, JLS在§15.11.2中给出了我们的答案。 “使用超级访问超类成员” (强调我的):

假设一个字段访问expression式super.f出现在C类中,并且C的直接超类是S类。如果S中的f可以从C类访问(§6.6),那么super.f被当作是在类S的主体中expressionthis.f。否则,会发生编译时错误。

因为所有字段都在相同的封闭类中,所以给出了可访问性。 他们可以是私人的,但仍然可以访问。

问题是在T2T3直接超类)中super.t1的处理是this.t1是非法的 – 在T2没有字段t1 。 因此编译器错误。