52

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 .

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
        }
    }
}
Community
  • 1
  • 1
andyf
  • 3,262
  • 3
  • 23
  • 37
  • 4
    You're mixing two different things there: Class *nesting* (inner classes), and *subclassing*. But it's quite an interesting question. `T3` not being able to access `super.t1` makes sense; `T3`'s `super` doesn't have a `t1`. I have to admit not understanding why `T3` can access `t2` though. Inner classes are weird. :-) – T.J. Crowder Jul 25 '16 at 05:47
  • 1
    @T.J.Crowder Yes, but why `T2` can access `t1`... and only `T3` can NOT access `t1` ? – andyf Jul 25 '16 at 05:48
  • The simple answer is that `private` takes effect outside the outer class. – user207421 Jul 25 '16 at 05:55
  • 10
    To add to that in most countries children accessing parent's privates is illegal – DevNewb Jul 25 '16 at 06:55
  • Possible duplicate of [Access to superclass private fields using the super keyword in a subclass](http://stackoverflow.com/questions/31478718/access-to-superclass-private-fields-using-the-super-keyword-in-a-subclass) – Raedwald Jul 25 '16 at 07:06
  • 5
    @DevNewb that was unnecessary... – DividedByZero Jul 25 '16 at 12:35
  • 4
    ...But still funny. – Jared Smith Jul 25 '16 at 15:40

3 Answers3

52

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.

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.

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

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

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

System.out.println(t1);

If super.super were allowed you'd be able to do this from 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?

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

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.

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.

Community
  • 1
  • 1
dimo414
  • 47,227
  • 18
  • 148
  • 244
  • So `T3` can access `t2` because `T3` is part of `T`, and `T2` is part of `T`? – T.J. Crowder Jul 25 '16 at 05:52
  • 9
    All of these classes have access to each others `private` fields, since they're all in the same outer-class. – dimo414 Jul 25 '16 at 05:53
  • @dimo414 But for normal inheritance i could refer to `t1` at each point of the inheritance by invoking `super.t1`. Why does it act different here? – SomeJavaGuy Jul 25 '16 at 05:56
  • 1
    What do you mean by "normal inheritance"? If these were all in separate files you wouldn't be able to address the parent classes private fields even via `super`. – dimo414 Jul 25 '16 at 05:58
  • @dimo414 if i´d define 3 classes, `A`, `B`, `C`, `A` having a `protected` field `t1` and `B` wouldinherit from `A` and `C` from `B`, `C` could refer to `A`´s `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 beeing in the same class? Or do i have a logical fault here? – SomeJavaGuy Jul 25 '16 at 06:01
  • 2
    @KevinEsche The difference is: Members declared as `protected` are _inherited_, whereas members declared as `private` are not. That means a field `protected int t1` is also a member of `B` (or `T2` as in the example), thus the access with `super.t2` in `C` (or `T3`) is allowed. – Seelenvirtuose Jul 25 '16 at 06:05
  • @KevinEsche updated my answer with your question; let me know if that still doesn't help. – dimo414 Jul 25 '16 at 06:12
15

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.

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.

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.

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

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.

charlie
  • 1,478
  • 10
  • 20
  • Thanks, didn't know any about `synthetic` until your answer. – andyf Jul 25 '16 at 09:24
  • @andyf: Check the update, I fixed the reference to the enclosing class `T`, which is propagated in another way -- through generated constructors and a synthetic member field. Note also, that the generated result varies between different compilers (openjdk, oracle, ibm, ...) -- they use different chaining patterns. – charlie Jul 25 '16 at 09:44
  • Synthetic methods are an implementation detail of the compiler, not part of the language spec. They're necessary because nested classes (and the unusual visibility privileges they have) weren't introduced until Java 1.2 and needed to work with prior bytecode. The synthetic `access$t1()` method makes `t1` accessible to both `T2` and `T3` since it's package-private. If the compiler wanted to it could add calls to `access$t1()` in `T3`. – dimo414 Jul 25 '16 at 16:25
  • @dimo414: But then it should be a part of JVM spec, shouldn't it? And **all** compilers need to work it around somehow (e.g. like above). Also, I don't get your last sentence, since there **is** a call to `T1.access$t1` in `T3` (eclipse compiler), no chaining. – charlie Jul 25 '16 at 20:59
  • The JVM spec and the language spec are different, and the latter isn't concerned with `synthetic` methods (though it might mention them in passing). They're an implementation detail to workaround a change in the language. My point in my last sentence was that there's nothing forbidding/preventing `T3` from calling `access$t1()` (if the compiler inserts it) according to the language spec. Put another way `T3` is permitted to access `t1` fields by the language, therefore the compiler is allowed to add calls to `access$t1()` inside `T3`. – dimo414 Jul 25 '16 at 21:56
  • 1
    My point is that the existence of `synthetic` methods doesn't explain OPs issue. – dimo414 Jul 25 '16 at 21:56
  • 1
    @dimo414: Yes, it doesn't, but your answer already did. Mine was complementary. – charlie Jul 26 '16 at 07:01
  • 1
    @dimo414 Your post really answers the question. But I'm really glad for the link to visible compiler internals, because "synthetic" is visible via Reflection. – A.H. Jul 28 '16 at 05:30
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):

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.

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.

Seelenvirtuose
  • 20,273
  • 6
  • 37
  • 66
  • Thanks for your answer, it explained how `super` works. But i accept another answer because the code `System.out.println(((T1)this).t1);` much easy to understand. – andyf Jul 25 '16 at 09:16