Ключевое слово super в Java - Вопросы

Всего: 6 вопросов

1. 

Что такое ключевое слово super в Java и какие у него есть формы?

super — это ссылка на непосредственный суперкласс (родительский класс) текущего объекта, то есть на ту часть объекта, которая пришла от предка. У ключевого слова две формы:

1. Вызов конструктора суперклассаsuper(списокАргументов);. Допустим только внутри конструктора и только один раз.

2. Обращение к члену суперклассаsuper.member: к полю, скрытому одноимённым полем наследника, или к методу, переопределённому в наследнике.

Важно: super — не объект и не переменная. Записать Object o = super; нельзя: это специальная форма доступа, которую компилятор разворачивает в невиртуальный вызов (инструкция invokespecial в байт-коде). В статическом методе и статическом блоке super недоступен, потому что объекта ещё нет.

2. 

Почему код перестаёт компилироваться с ошибкой there is no default constructor available и кто подставляет неявный super()?

Если в конструкторе наследника нет явного вызова super(...) или this(...), компилятор (не JVM) сам подставляет super() без аргументов первой строкой:

public HeavyBox() {
    super();          // подставлено компилятором
    this.weight = -1;
}

Как только в суперклассе объявлен хотя бы один конструктор с параметрами, конструктор по умолчанию перестаёт генерироваться. Неявному super() становится не к чему привязаться — и сборка падает с сообщением there is no default constructor available in Box, причём в файлах наследников, которые вы не трогали.

Два способа починить: добавить в родителя конструктор без параметров либо явно вызвать существующий конструктор — super(width, height, depth);.

3. 

В каком порядке выполняются конструкторы при создании объекта в многоуровневой иерархии классов?

Тела конструкторов выполняются от корня иерархии к текущему классу. Для иерархии SuperSuperClassSuperClassSomeClass вызов new SomeClass() даёт вывод:

В конструкторе SuperSuperClass
В конструкторе SuperClass
В конструкторе SomeClass

Механика такая: каждый конструктор сначала передаёт управление наверх по цепочке неявных или явных super() — до самого java.lang.Object, — и только на обратном пути выполняются тела, сверху вниз, от предка к потомку. Смысл в том, что к моменту старта тела конструктора наследника унаследованная часть объекта уже полностью проинициализирована.

Обратный порядок в логах — классическая ловушка на собеседовании.

4. 

Зачем нужна форма super.member и почему super.print() внутри переопределённого метода print() не вызывает бесконечную рекурсию?

super.member нужен только тогда, когда имя перекрыто в наследнике: в остальных случаях унаследованные члены и так видны по простому имени.

public class D extends C {
    public String i;          // скрывает int i из класса C

    public D(String a, int b) {
        i = a;
        super.i = b;          // единственный способ добраться до int i
    }

    public void print() {
        System.out.println("D.i = " + i);
        super.print();        // вызов реализации класса C
    }
}

Поля скрываются (hiding) и разрешаются по типу ссылки, а методы переопределяются (overriding) и разрешаются по реальному типу объекта. Обычный вызов print() внутри D.print() ушёл бы обратно в D.print() — то есть в бесконечную рекурсию. super.print() компилируется в невиртуальный вызов (invokespecial), который жёстко привязан к реализации суперкласса, поэтому рекурсии не возникает.

Форма super.super.method() в Java запрещена: перепрыгнуть через уровень иерархии нельзя. Для default-метода интерфейса используется квалифицированная форма ИмяИнтерфейса.super.method(), например Walkable.super.move().

5. 

Чем отличаются super(...), this(...), super.member и this.member? Можно ли вызвать super() и this() в одном конструкторе?

super(...) вызывает конструктор суперкласса, this(...) — другой конструктор этого же класса. Обе конструкции допустимы только внутри конструктора и только один раз.

super.member обращается к скрытому полю или переопределённому методу предка, this.member — к полю или методу текущего объекта. Эти две формы доступны в любом методе экземпляра и в конструкторе.

Нет, super(...) и this(...) взаимоисключающие — в одном конструкторе допускается ровно один явный вызов. Если конструктор начинается с this(...), компилятор не подставляет неявный super(): конструктор суперкласса отработает в том конструкторе, которому вы делегировали работу. В итоге на один создаваемый объект приходится ровно один вызов super(...).

6. 

Что такое Flexible Constructor Bodies, с какой версии Java можно писать код перед super() и что запрещено делать в прологе конструктора?

Исторически super() или this() обязаны были быть первым оператором конструктора. Ограничение снимали постепенно: JEP 447 (Java 22, preview) → JEP 482 (Java 23) → JEP 492 (Java 24) → JEP 513 (Java 25) — финальная фича, флаг --enable-preview не нужен. На LTS-версиях Java 17 и Java 21 действует старое правило.

Тело конструктора делится на пролог (код до super()/this()) и эпилог (код после). Главное правило пролога: обращаться к создаваемому объекту ещё нельзя. В прологе можно присваивать значения полям текущего класса, вызывать static-методы и выполнять любые локальные вычисления, включая проверки, циклы и throw. Нельзя читать поля, трогать унаследованные поля, вызывать методы экземпляра и передавать this куда-либо.

public HeavyBox(int width, int height, int depth, int weight) {
    if (width <= 0) {
        throw new IllegalArgumentException("width must be positive");
    }
    super(width, height, depth);
    this.weight = weight;
}

Пишете под Java 17/21 — выносите подготовку аргументов в приватный static-метод и вызывайте его прямо в super(...).

Страница 1 из 1