Почему вложенные дочерние классы могут обращаться к закрытым членам своего родительского класса, но внуки не могут?
Вероятно, похоже на вопрос Почему внешние классы Java могут получить доступ к закрытым членам внутреннего класса? или Доступ к закрытым полям суперкласса используя ключевое слово super в подклассе.
Но есть некоторые отличия: класс children может обращаться к закрытым членам своего родительского класса (и только к ближайшему родительскому классу).
Учитывая пример кода ниже:
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
Умный пример! Но это на самом деле несколько скучное объяснение - нет проблемы видимости, вы просто не имеете возможности ссылаться на t1
непосредственно из T3
, потому что super.super
не разрешено.
T2
не может получить доступ к своему собственному полю t1
напрямую, поскольку он является частным (а дочерние классы не наследуют их родительские частные поля), но super
фактически является экземпляром t1
, и поскольку он в тот же класс T2
может ссылаться на частные поля super
. Просто нет механизма для T3
для прямого доступа к частным полям его класса grandparent t1
.
Оба эти компилируются как раз тонкие внутри T3
, что демонстрирует, что T3
может получить доступ к полям grandparent private
:
System.out.println(((T1)this).t1);
System.out.println(new T1().t1);
И наоборот, это не компилируется ни в T2
, ни в T3
:
System.out.println(t1);
Если super.super
было разрешено, вы сможете сделать это из T3
:
System.out.println(super.super.t1);
если бы я определил 3 класса, A
, B
, C
, A
с защищенным полем t1
и B
наследовал бы от A
и C
от B
, C
может ссылаться на A
t1
, вызывая super.t1
, потому что это видно здесь. логически не должны совпадать с наследованием внутренних классов, даже если поле является частным, потому что эти частные члены должны быть видимыми из-за того, что они находятся в одном классе?
(Я просто хочу использовать имена классов OP t1
, T2
и T3
)
Если t1
были protected
, не было бы проблем - T3
мог ссылаться на поле t1
прямо так же, как любой подкласс. Проблема возникает с private
, потому что класс не знает поля родительских классов private
и поэтому не может ссылаться на них напрямую, хотя на практике они видны. Вот почему вы должны использовать super.t1
от T2
, чтобы даже ссылаться на соответствующее поле.
Несмотря на то, что в отношении T3
он не имеет поля t1
, он имеет доступ к полям t1
private
, находясь в том же внешнем классе. Поскольку все, что вам нужно сделать, - это сделать this
до t1
, и у вас есть способ обратиться к частному полю. Вызов super.t1
в T2
- это (по сути) литье this
в t1
, позволяющее нам ссылаться на его поля.
Ответ 2
Методы синтетического доступа
Технически, на уровне JVM вы можете НЕ получить доступ к любым членам private
другого класса — не входящих в класс (T.t
), а не класса родительского класса (T2.t2
). В вашем коде это просто выглядит как, потому что компилятор генерирует synthetic
методы доступа для вас в классах доступа. То же самое происходит, когда в классе T3
вы исправляете недопустимую ссылку super.t1
, используя правильную форму ((T1) this).t1
.
С помощью такого компилятора сгенерированный метод доступа synthetic
, вы можете в общем доступе любой private
член любой класс, вложенный в внешний (верхний уровень) класс T
, например от T1
вы можете использовать new T2().t2
. Обратите внимание, что это относится и к членам private static
.
Атрибут synthetic
был представлен в версии 1.1 JDK для поддержки вложенных классов, новой языковой функции в java в то время. С тех пор JLS явно разрешает взаимный доступ ко всем членам класса верхнего уровня, включая private
ones.
Но для обратной совместимости компилятор разворачивает вложенные классы (например, до T$T1
, T$T2
, T$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());
самостоятельно.
Ответ 3
Очень хорошая находка! Я думаю, мы все предположили, что ваш пример кода должен компилироваться.
К сожалению, это не так... и JLS дает нам ответ в §15.11. 2. "Доступ к членам суперкласса с использованием супер" (выделено мной):
Предположим, что выражение класса super.f для поля доступно в классе C, а суперкласс класса немедленный C - класс S. Если f в S доступен из класса C (§6.6), то супер .f рассматривается так, как если бы это было выражение this.f в теле класса S. В противном случае возникает ошибка времени компиляции.
Доступность задается, потому что все поля находятся в одном классе. Они могут быть частными, но все еще доступны.
Проблема заключается в том, что в T2
(непосредственном суперклассе T3
) обработка super.t1
как this.t1
является незаконной - в T2
нет поля t1
. Следовательно, ошибка компилятора.