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

2022-08-31 20:59:37

可能类似于以下问题:为什么外部 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
        }
    }
}

答案 1

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

T2不能直接访问自己的字段,因为它是私有的(并且子类不继承其父级的私有字段),但它实际上是的实例,并且由于它在同一类中可以引用 的私有字段。只是没有机制可以直接解决其祖级类的私有字段。t1superT1T2superT3T1

这两者都在 里面编译得很好,这表明 a 可以访问其祖父母的字段:T3T3private

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

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

System.out.println(t1);

如果允许,您可以从以下位置执行此操作:super.superT3

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

如果我定义 3 个类, 、 、 、 、 具有受保护字段,并且将从 继承和从 继承,则可以通过调用来引用 s,因为它在这里是可见的。从逻辑上讲,即使字段是私有的,这不应该同样适用于内部类继承,因为这些私有成员应该由于位于同一类中而可见吗?ABCAt1BACBCAt1super.t1

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

如果没有问题 - 可以直接引用字段,就像任何子类一样。问题在于,一个类不知道其父类的字段,因此无法直接引用它们,即使它们在实践中是可见的。这就是为什么您必须使用 from 的原因,以便甚至引用所讨论的字段。t1protectedT3t1privateprivatesuper.t1T2

即使就 a 而言,它没有字段,它也可以通过位于同一外部类中来访问 s 字段。既然如此,您需要做的就是强制转换为 a,并且您有办法引用私有字段。调用(实质上)是转换为让我们引用其字段。T3t1T1privatethisT1super.t1T2thisT1


答案 2

系统访问器方法

从技术上讲,在 JVM 级别,您不能访问另一个类的任何成员 — 无论是封闭类 () 的成员,还是父类 () 的成员。在代码中,它看起来就像你可以,因为编译器在访问的类中为你生成综合访问器方法。在类中使用正确的形式修复无效的引用 super.t1 时,也会发生同样的情况。privateT.tT2.t2T3((T1) this).t1

借助这种编译器生成的综合访问器方法,通常可以访问嵌套在外部(顶级)类中的任何的任何成员,例如,从您可以使用.请注意,这也适用于成员。privateTT1new T2().t2private static

合成属性是在 JDK 版本 1.1 中引入的,以支持嵌套类,这是当时 Java 中的新语言特性。从那时起,JLS明确允许对顶级类中的所有成员进行相互访问,包括一个成员。private

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

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)
    }
}

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