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

时间:2022-05-27 23:06:09

Probably similar to the question, Why can outer Java classes access inner class private members? or Access to superclass private fields using the super keyword in a subclass .

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

But there are some differences: the children class can access the private members of their parent (and only the nearest parent) class.

但是存在一些差异:子类可以访问其父级(并且只有最近的父级)的私有成员。

Given the sample code below:

鉴于以下示例代码:

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

3 个解决方案

#1


49  

Clever example! But it's actually a somewhat boring explanation - there's no visibility problem, you simply have no way of referring to t1 directly from T3 because super.super isn't allowed.

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

T2 can't access its own t1 field directly since it's private (and child classes don't inherit their parent's private fields), but super is effectively an instance of T1 and since it's in the same class T2 can refer to the private fields of super. There's just no mechanism for T3 to address the private fields of its grandparent class T1 directly.

T2不能直接访问自己的t1字段,因为它是私有的(并且子类不继承其父级的私有字段),但是super实际上是T1的一个实例,因为它在同一个类T2中可以引用私有字段超。 T3没有直接处理其祖父级T1的私有字段的机制。

Both of these compile just fine inside T3, which demonstrates that a T3 can access its grandparent's private fields:

这两个都在T3内编译得很好,这表明T3可以访问其祖父母的私有字段:

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

Conversely this doesn't compile in either T2 or T3:

相反,这不会在T2或T3中编译:

System.out.println(t1);

If super.super were allowed you'd be able to do this from T3:

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

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

if I'd define 3 classes, A, B, C, A having a protected field t1 and B would inherit from A and C from B, C could refer to As t1 by invoking super.t1 because it´s visible here. logically shouldn't the same apply to inner classes inheritance even if the field are private, because these private members should be visible due to being in the same class?

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

(I'm going to stick with OP's T1, T2, and T3 class names for simplicity)

(为简单起见,我将坚持使用OP的T1,T2和T3类名称)

If t1 were protected there'd be no problem - T3 could refer to the t1 field directly just like any subclass. The issue arises with private because a class has no awareness of its parent classes' private fields, and therefore can't reference them directly, even though in practice they are visible. That's why you have to use super.t1 from T2, in order to even refer to the field in question.

如果t1受到保护则没有问题--T3可以像任何子类一样直接引用t1字段。私有问题出现了,因为一个类没有意识到它的父类的私有字段,因此不能直接引用它们,即使在实践中它们是可见的。这就是为什么你必须使用T2中的super.t1,以便甚至引用有问题的字段。

Even though as far as a T3 is concerned it has no t1 field it has access into T1s private fields by being in the same outer class. Since that's the case all you need to do is cast this to a T1 and you have a way to refer to the private field. The super.t1 call in T2 is (in essence) casting this into a T1 letting us refer to its fields.

尽管就T3来说它没有t1字段,它可以通过在同一个外部类中访问T1私有字段。既然如此,您需要做的就是将其转换为T1并且您可以引用私有字段。 T2中的super.t1调用(实质上)将其转换为T1,让我们引用它的字段。

#2


14  

Sythetic Accessor Methods

Technically, on the JVM level, you can NOT access any private members of another class — neither those of an enclosing class (T.t), nor those of a parent class (T2.t2). In your code it just looks like you can, because the compiler generates synthetic accessor methods for you in the accessed classes. The same happens when in the T3 class you fix the invalid reference super.t1 using the correct form ((T1) this).t1.

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

With the help of such a compiler generated synthetic accessor method, you can in general access any private member of any class nested in the outer (top level) T class, e.g. from T1 you can use new T2().t2. Note that this applies to private static members too.

在这种编译器生成的合成访问器方法的帮助下,您通常可以访问嵌套在外部(*)T类中的任何类的任何私有成员,例如,从T1你可以使用新的T2()。t2。请注意,这也适用于私有静态成员。

The synthetic attribute was introduced in JDK release 1.1 to support nested classes, a new language feature in java at that time. Since then the JLS explicitly allows mutual access to all members within a top level class, including private ones.

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

But for backwards compatibility, the compiler unwraps nested classes (e.g. to T$T1, T$T2, T$T3) and translates private member accesses to calls to generated synthetic accessor methods (these methods thus need to have the package private, i.e. default, visibility):

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

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.: You're not allowed to refer to synthetic members directly, so in the source code you can't use e.g. int i = T.access$t(new T()); yourself.

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

#3


7  

Very good finding! I think, we all had assumed that your code example should compile.

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

Unfortunately, it is not the case ... and the JLS gives us an answer in §15.11.2. "Accessing Superclass Members using super" (emphasis mine):

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

Suppose that a field access expression super.f appears within class C, and the immediate superclass of C is class S. If f in S is accessible from class C (§6.6), then super.f is treated as if it had been the expression this.f in the body of class S. Otherwise, a compile-time error occurs.

假设字段访问表达式super.f出现在类C中,并且C的直接超类是类S.如果S中的f可以从类C(第6.6节)访问,则super.f被视为如果它是在类S的主体中表达this.f.否则,发生编译时错误。

Accessibility is given because all fields are in the same enclosing class. They can be private but are still accessible.

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

The problem is that in T2 (the immediate superclass of T3) the treatment of super.t1 as this.t1 is illegal - there is no field t1 in T2. Hence the compiler error.

问题是在T2(T3的直接超类)中,super.t1作为this.t1的处理是非法的 - 在T2中没有字段t1。因此编译错误。

#1


49  

Clever example! But it's actually a somewhat boring explanation - there's no visibility problem, you simply have no way of referring to t1 directly from T3 because super.super isn't allowed.

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

T2 can't access its own t1 field directly since it's private (and child classes don't inherit their parent's private fields), but super is effectively an instance of T1 and since it's in the same class T2 can refer to the private fields of super. There's just no mechanism for T3 to address the private fields of its grandparent class T1 directly.

T2不能直接访问自己的t1字段,因为它是私有的(并且子类不继承其父级的私有字段),但是super实际上是T1的一个实例,因为它在同一个类T2中可以引用私有字段超。 T3没有直接处理其祖父级T1的私有字段的机制。

Both of these compile just fine inside T3, which demonstrates that a T3 can access its grandparent's private fields:

这两个都在T3内编译得很好,这表明T3可以访问其祖父母的私有字段:

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

Conversely this doesn't compile in either T2 or T3:

相反,这不会在T2或T3中编译:

System.out.println(t1);

If super.super were allowed you'd be able to do this from T3:

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

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

if I'd define 3 classes, A, B, C, A having a protected field t1 and B would inherit from A and C from B, C could refer to As t1 by invoking super.t1 because it´s visible here. logically shouldn't the same apply to inner classes inheritance even if the field are private, because these private members should be visible due to being in the same class?

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

(I'm going to stick with OP's T1, T2, and T3 class names for simplicity)

(为简单起见,我将坚持使用OP的T1,T2和T3类名称)

If t1 were protected there'd be no problem - T3 could refer to the t1 field directly just like any subclass. The issue arises with private because a class has no awareness of its parent classes' private fields, and therefore can't reference them directly, even though in practice they are visible. That's why you have to use super.t1 from T2, in order to even refer to the field in question.

如果t1受到保护则没有问题--T3可以像任何子类一样直接引用t1字段。私有问题出现了,因为一个类没有意识到它的父类的私有字段,因此不能直接引用它们,即使在实践中它们是可见的。这就是为什么你必须使用T2中的super.t1,以便甚至引用有问题的字段。

Even though as far as a T3 is concerned it has no t1 field it has access into T1s private fields by being in the same outer class. Since that's the case all you need to do is cast this to a T1 and you have a way to refer to the private field. The super.t1 call in T2 is (in essence) casting this into a T1 letting us refer to its fields.

尽管就T3来说它没有t1字段,它可以通过在同一个外部类中访问T1私有字段。既然如此,您需要做的就是将其转换为T1并且您可以引用私有字段。 T2中的super.t1调用(实质上)将其转换为T1,让我们引用它的字段。

#2


14  

Sythetic Accessor Methods

Technically, on the JVM level, you can NOT access any private members of another class — neither those of an enclosing class (T.t), nor those of a parent class (T2.t2). In your code it just looks like you can, because the compiler generates synthetic accessor methods for you in the accessed classes. The same happens when in the T3 class you fix the invalid reference super.t1 using the correct form ((T1) this).t1.

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

With the help of such a compiler generated synthetic accessor method, you can in general access any private member of any class nested in the outer (top level) T class, e.g. from T1 you can use new T2().t2. Note that this applies to private static members too.

在这种编译器生成的合成访问器方法的帮助下,您通常可以访问嵌套在外部(*)T类中的任何类的任何私有成员,例如,从T1你可以使用新的T2()。t2。请注意,这也适用于私有静态成员。

The synthetic attribute was introduced in JDK release 1.1 to support nested classes, a new language feature in java at that time. Since then the JLS explicitly allows mutual access to all members within a top level class, including private ones.

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

But for backwards compatibility, the compiler unwraps nested classes (e.g. to T$T1, T$T2, T$T3) and translates private member accesses to calls to generated synthetic accessor methods (these methods thus need to have the package private, i.e. default, visibility):

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

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.: You're not allowed to refer to synthetic members directly, so in the source code you can't use e.g. int i = T.access$t(new T()); yourself.

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

#3


7  

Very good finding! I think, we all had assumed that your code example should compile.

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

Unfortunately, it is not the case ... and the JLS gives us an answer in §15.11.2. "Accessing Superclass Members using super" (emphasis mine):

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

Suppose that a field access expression super.f appears within class C, and the immediate superclass of C is class S. If f in S is accessible from class C (§6.6), then super.f is treated as if it had been the expression this.f in the body of class S. Otherwise, a compile-time error occurs.

假设字段访问表达式super.f出现在类C中,并且C的直接超类是类S.如果S中的f可以从类C(第6.6节)访问,则super.f被视为如果它是在类S的主体中表达this.f.否则,发生编译时错误。

Accessibility is given because all fields are in the same enclosing class. They can be private but are still accessible.

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

The problem is that in T2 (the immediate superclass of T3) the treatment of super.t1 as this.t1 is illegal - there is no field t1 in T2. Hence the compiler error.

问题是在T2(T3的直接超类)中,super.t1作为this.t1的处理是非法的 - 在T2中没有字段t1。因此编译错误。