为什么嵌套的子类可以访问其父类的私有成员,但是孙子们不能?
可能类似于这个问题, 为什么外部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
中可以引用私有字段super
。 T3
没有任何机制直接解决祖父级T1
的私有领域。
T3
这两种编译都很好,这表明T3
可以访问其祖父母的private
字段:
System.out.println(((T1)this).t1); System.out.println(new T1().t1);
相反,这不能在T2
或T3
编译:
System.out.println(t1);
如果super.super
被允许,你可以从T3
做到这一点:
System.out.println(super.super.t1);
如果我定义了3个类,具有保护字段
t1
和B
A
,B
,C
,A
将从B
inheritanceA
和C
,C
可以通过调用super.t1
来引用A
st1
,因为在这里可见。 从逻辑上讲,不应该同样适用于内部类inheritance,即使该字段是私有的,因为这些私有成员应该是可见的,因为在同一个类中?
(为了简单起见,我将坚持使用OP的T1
, T2
和T3
类名)
如果t1
被protected
,就没有问题了 – T3
可以像任何子类一样直接引用t1
字段。 这个问题出现在private
因为一个阶级没有意识到其父类的private
领域,因此不能直接引用它们,即使在实践中它们是可见的。 这就是为什么你必须使用T2
super.t1
,以便甚至可以参考相关字段。
即使就T3
而言,它也没有t1
字段,它可以通过在同一个外部类中进入T1
的private
字段。 既然是这样的话,所有你需要做的就是将this
转换为T1
并且你有办法引用私有字段。 T2
的super.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$T1
, T$T2
, T$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。否则,会发生编译时错误。
因为所有字段都在相同的封闭类中,所以给出了可访问性。 他们可以是私人的,但仍然可以访问。
问题是在T2
( T3
的直接超类)中super.t1
的处理是this.t1
是非法的 – 在T2
没有字段t1
。 因此编译器错误。