Why can nested child classes access private members of their parent class, but grandchildren cannot?
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 fieldt1
andB
would inherit fromA
andC
fromB
,C
could refer toA
st1
by invokingsuper.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 T1
s 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.
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 using the correct form super.t1
((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.