Подтвердить что ты не робот

Почему вложенные дочерние классы могут обращаться к закрытым членам своего родительского класса, но внуки не могут?

Вероятно, похоже на вопрос Почему внешние классы 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
        }
    }
}
4b9b3361

Ответ 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. Следовательно, ошибка компилятора.