为啥嵌套的子类可以访问其父类的私有成员,而孙子却不能?

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 是有道理的; T3super 没有 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$T1T$T2T$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);

相反,这不会在 T2T3 中编译:

System.out.println(t1);

如果允许super.super,您就可以从T3 执行此操作:

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

如果我定义 3 个类,ABCA 具有受保护字段 t1B 将从 AC 继承自 @987654351 @、C 可以通过调用 super.t1 来引用 As t1,因为它在这里可见。即使字段是私有的,逻辑上也不应该同样适用于内部类继承,因为这些私有成员应该是可见的,因为它们在同一个类中?

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

如果 t1protected 就没有问题 - T3 可以像任何子类一样直接引用 t1 字段。 private 会出现问题,因为一个类没有了解其父类的private 字段,因此无法直接引用它们,即使在实践中它们是可见的。这就是为什么您必须使用 T2 中的 super.t1 才能引用相关字段。

尽管就T3 而言,它没有t1 字段,但它可以通过在同一个外部类中访问T1s private 字段。既然是这种情况,您只需将this 转换为T1,您就有办法引用私有字段。 T2 中的 super.t1 调用(本质上)是将 this 转换为 T1 让我们引用它的字段。

【讨论】:

所以T3 可以访问t2,因为T3T 的一部分,而T2T 的一部分? 所有这些类都可以访问彼此的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。否则,会发生编译时错误。

提供可访问性是因为所有字段都在同一个封闭类中。它们可以是私有的,但仍然可以访问。

问题是在T2T3immediate 超类)中,将super.t1 视为this.t1 是非法的 - 在@987654328 中没有字段t1 @。因此编译器错误。

【讨论】:

感谢您的回答,它解释了super 的工作原理。但我接受另一个答案,因为代码 System.out.println(((T1)this).t1); 很容易理解。

以上是关于为啥嵌套的子类可以访问其父类的私有成员,而孙子却不能?的主要内容,如果未能解决你的问题,请参考以下文章

继承 Java 私有成员

访问超类的私有成员

C#中继承类为啥可以通过属性访问基类的私有字段。

java中私有的属性、静态成员可以被子类继承吗?

继承的基本概念: Java不支持多继承,也就是说子类至多只能有一个父类。 子类继承了其父类中不是私有的成员变量和成员方法,作为自己的成员变量和方法。 子类中定义的成员变量和父类中

Java子类访问父类的私有成员变量