为啥嵌套的子类可以访问其父类的私有成员,而孙子却不能?
Posted
技术标签:
【中文标题】为啥嵌套的子类可以访问其父类的私有成员,而孙子却不能?【英文标题】:Why can nested child classes access private members of their parent class, but grandchildren cannot?为什么嵌套的子类可以访问其父类的私有成员,而孙子却不能? 【发布时间】:2016-11-28 08:43:51 【问题描述】:可能与问题类似,Why can outer Java classes access inner class private members? 或 Access to superclass private fields using the super keyword in a subclass。
但有一些区别:子类可以访问其父类(并且只能访问最近的父类)的私有成员。
给出下面的示例代码:
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
【问题讨论】:
你在这里混合了两种不同的东西:nesting类(内部类)和subclassing。但这是一个非常有趣的问题。T3
无法访问 super.t1
是有道理的; T3
的 super
没有 t1
。我不得不承认不明白为什么T3
可以访问t2
。内部课程很奇怪。 :-)
@T.J.Crowder 是的,但是为什么T2
可以访问t1
... 而只有T3
不能访问t1
?
此外,在大多数国家,儿童访问父母的隐私是非法的
@DevNewb 这是不必要的......
...但还是很有趣。
【参考方案1】:
合成存取方法
从技术上讲,在 JVM 级别上,您可以不访问另一个类的任何 private
成员——既不能访问封闭类 (T.t
) 的成员,也不能访问父类的那些(T2.t2
)。在您的代码中,它看起来像,因为编译器会在访问的类中为您生成synthetic
访问器方法。当您在 T3
类中使用正确的格式 ((T1) this).t1
修复无效引用 时,也会发生同样的情况。super.t1
借助这种编译器生成的 synthetic
访问器方法,您通常可以访问 any private
any 的成员嵌套在外部(顶层)T
类中的类,例如来自T1
,您可以使用new T2().t2
。请注意,这也适用于private static
成员。
synthetic
属性是在 JDK 1.1 版中引入的,以支持嵌套类,这是当时 java 中的一种新语言特性。从那时起,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 t.t;
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)
N.B.: 你不能直接引用 synthetic
成员,所以在源代码中你不能使用例如int i = T.access$t(new T());
你自己。
【讨论】:
谢谢,在您回答之前对synthetic
一无所知。
@andyf:检查更新,我修复了对封闭类T
的引用,它以另一种方式传播——通过生成的构造函数和合成成员字段。另请注意,生成的结果因不同的编译器(openjdk、oracle、ibm、...)而异——它们使用不同的链接模式。
我的意思是 synthetic
方法的存在并不能解释 OPs 问题。
@dimo414:是的,它没有,但你的答案已经做到了。我的是互补的。
@dimo414 您的帖子确实回答了这个问题。但我真的很高兴链接到可见的编译器内部,因为通过反射可以看到“合成”。【参考方案2】:
聪明的例子!但这实际上是一个有点无聊的解释——不存在可见性问题,你根本无法直接从T3
引用t1
,因为super.super
isn't allowed。
T2
不能直接访问它自己的 t1
字段,因为它是私有的(并且子类不继承其父类的私有字段),但 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 个类,
A
、B
、C
、A
具有受保护字段t1
和B
将从A
和C
继承自 @987654351 @、C
可以通过调用super.t1
来引用A
st1
,因为它在这里可见。即使字段是私有的,逻辑上也不应该同样适用于内部类继承,因为这些私有成员应该是可见的,因为它们在同一个类中?
(为简单起见,我将坚持使用 OP 的 T1
、T2
和 T3
类名)
如果 t1
是 protected
就没有问题 - T3
可以像任何子类一样直接引用 t1
字段。 private
会出现问题,因为一个类没有了解其父类的private
字段,因此无法直接引用它们,即使在实践中它们是可见的。这就是为什么您必须使用 T2
中的 super.t1
才能引用相关字段。
尽管就T3
而言,它没有t1
字段,但它可以通过在同一个外部类中访问T1
s private
字段。既然是这种情况,您只需将this
转换为T1
,您就有办法引用私有字段。 T2
中的 super.t1
调用(本质上)是将 this
转换为 T1
让我们引用它的字段。
【讨论】:
所以T3
可以访问t2
,因为T3
是T
的一部分,而T2
是T
的一部分?
所有这些类都可以访问彼此的private
字段,因为它们都在同一个外部类中。
@dimo414 但是对于正常的继承,我可以通过调用super.t1
在继承的每个点引用t1
。为什么它在这里表现不同?
“正常继承”是什么意思?如果这些都在单独的文件中,即使通过super
,您也无法处理父类的私有字段。
@KevinEsche 不同之处在于:声明为protected
的成员是继承的,而声明为private
的成员则不是。这意味着字段protected int t1
也是B
的成员(或示例中的T2
),因此允许在C
(或T3
)中使用super.t2
进行访问。【参考方案3】:
很好的发现!我想,我们都假设你的代码示例应该编译。
不幸的是,事实并非如此……JLS 在§15.11.2. "Accessing Superclass Members using super" 中为我们提供了答案(强调我的):
假设字段访问表达式 super.f 出现在类 C 中,并且 C 的 立即 超类是类 S。如果 S 中的 f 可以从类 C 访问(第 6.6 节),则 super .f 被视为好像它是类 S 主体中的表达式 this.f。否则,会发生编译时错误。
提供可访问性是因为所有字段都在同一个封闭类中。它们可以是私有的,但仍然可以访问。
问题是在T2
(T3
的 immediate 超类)中,将super.t1
视为this.t1
是非法的 - 在@987654328 中没有字段t1
@。因此编译器错误。
【讨论】:
感谢您的回答,它解释了super
的工作原理。但我接受另一个答案,因为代码 System.out.println(((T1)this).t1);
很容易理解。以上是关于为啥嵌套的子类可以访问其父类的私有成员,而孙子却不能?的主要内容,如果未能解决你的问题,请参考以下文章
继承的基本概念: Java不支持多继承,也就是说子类至多只能有一个父类。 子类继承了其父类中不是私有的成员变量和成员方法,作为自己的成员变量和方法。 子类中定义的成员变量和父类中