Ключевое слово super в Java
Вы добавили в родительский класс Box конструктор с параметрами — и проект перестал собираться: constructor Box in class Box cannot be applied to given types. При этом в классах-наследниках вы не тронули ни одной строки. Виноват невидимый вызов super(), который компилятор сам дописывает первой строкой каждого конструктора.
Ключевое слово super в Java — это ссылка на непосредственный суперкласс (родительский класс) текущего объекта. Оно используется в двух случаях: чтобы вызвать конструктор суперкласса (super(...)) и чтобы обратиться к полю или методу суперкласса, скрытому или переопределённому в наследнике (super.member).
1. Что такое ключевое слово super
Ключевое слово super очень похоже на ключевое слово this, которое мы рассматривали в одном из предыдущих уроков. Разница простая: this ссылается на текущий объект, а super — на ту его часть, которая пришла от родительского класса.
Суперкласс (superclass, родительский класс, предок) — это класс, от которого наследуется текущий класс. Если написано class HeavyBox extends Box, то Box — суперкласс, а HeavyBox — подкласс (наследник).
У ключевого слова super есть две формы:
- Вызов конструктора суперкласса. После
superставятся круглые скобки, в которых передаётся список аргументов:super(списокАргументов); - Обращение к полю или методу суперкласса, который скрыт или переопределён в подклассе:
super.member;
2. Вызов конструктора суперкласса через super()
Передача параметров в конструктор суперкласса
Если в иерархии классов требуется передать параметры конструктору суперкласса, то все подклассы должны передавать эти параметры вверх по иерархии. То есть из конструктора подкласса нужно вызвать конструктор суперкласса с помощью super().
В этом примере из конструктора класса HeavyBox вызывается конструктор класса Box с помощью super(), тем самым передаются необходимые значения:
public class Box {
double width;
double height;
double depth;
Box(double w, double h, double d) {
width = w;
height = h;
depth = d;
}
public Box() {
}
} public class HeavyBox extends Box {
int weight;
public HeavyBox(int width, int height, int depth, int weight) {
super(width, height, depth);
this.weight = weight;
}
public HeavyBox() {
this.weight = -1;
}
} Если в конструкторе наследника нет явного вызова super() — как во втором конструкторе класса HeavyBox — компилятор сам подставляет его первой строкой:
public HeavyBox() {
super(); // подставлено компилятором
this.weight = -1;
} Отсюда следует важное правило: у суперкласса должен быть конструктор без параметров, иначе неявный вызов super() не к чему привязать и возникает ошибка компиляции.
Важно
Неявный super() добавляет компилятор, а не JVM — поэтому проблема всплывает на этапе сборки. Если в классе Box удалить конструктор без параметров, компилятор сообщит: there is no default constructor available in Box. Как только вы объявили в классе хотя бы один конструктор с параметрами, конструктор по умолчанию перестаёт генерироваться автоматически.
Порядок вызова конструкторов в многоуровневой иерархии
Когда super() вызывается из подкласса, выполняется конструктор его непосредственного суперкласса. Это справедливо и для многоуровневой иерархии. Допустим, в иерархии есть три класса:

Есть SuperSuperClass, его расширяет SuperClass, а SomeClass расширяет SuperClass. Код этих классов предельно короткий — каждый конструктор просто печатает своё имя:
public class SuperSuperClass {
public SuperSuperClass() {
System.out.println("В конструкторе SuperSuperClass");
}
}
public class SuperClass extends SuperSuperClass {
public SuperClass() {
System.out.println("В конструкторе SuperClass");
}
}
public class SomeClass extends SuperClass {
public SomeClass() {
System.out.println("В конструкторе SomeClass");
}
} Создадим объект класса SomeClass:
SomeClass someClass = new SomeClass(); Вывод в консоль:
В конструкторе SuperSuperClass
В конструкторе SuperClass
В конструкторе SomeClass Хотя создаётся объект SomeClass, первым отрабатывает конструктор SuperSuperClass, затем SuperClass и только потом SomeClass. Причина в цепочке неявных super(): каждый конструктор сначала передаёт управление наверх, до самого корня иерархии (в конечном счёте до java.lang.Object), а тела конструкторов выполняются уже на обратном пути — сверху вниз, от предка к потомку. Логика простая: к моменту, когда выполняется тело конструктора наследника, унаследованная часть объекта уже полностью проинициализирована.
3. Код перед super(): Flexible Constructor Bodies
Исторически вызов super() обязан был быть первым оператором в конструкторе. Такой код до Java 22 не компилировался:
public class SomeClass extends SuperClass {
public SomeClass() {
System.out.println("В конструкторе SomeClass"); // ошибка компиляции до Java 22
super();
}
} Ограничение появилось из тех же соображений: сначала должен быть сконструирован суперкласс. Но оно оказалось излишне жёстким — валидацию и подготовку аргументов приходилось прятать в статические методы.
Постепенно ограничение сняли:
| Версия Java | JEP | Название | Статус |
|---|---|---|---|
| Java 22 | JEP 447 | Statements before super(...) | Preview (нужен --enable-preview) |
| Java 23 | JEP 482 | Flexible Constructor Bodies | Second preview |
| Java 24 | JEP 492 | Flexible Constructor Bodies | Third preview |
| Java 25 | JEP 513 | Flexible Constructor Bodies | Финальная фича, флаг не нужен |
Что изменилось
Начиная с Java 25 Flexible Constructor Bodies — обычная часть языка. Но на LTS-версиях Java 17 и Java 21, которые до сих пор стоят в большинстве продакшен-проектов, правило прежнее: super() или this() должны быть первой строкой конструктора. Пишете под Java 17/21 — выносите подготовку аргументов в приватный static-метод и вызывайте его прямо в super(...).
Пролог и эпилог конструктора
Тело конструктора делится на две части:
- пролог — код до вызова
super()илиthis(); - эпилог — код после него (либо всё тело, если явного вызова нет).
Главное правило пролога: в нём ещё нельзя обращаться к создаваемому объекту, потому что он не готов.
| В прологе | Можно | Нельзя |
|---|---|---|
| Поля текущего класса | Присваивать значение (this.x = 1;) | Читать значение |
| Унаследованные поля | — | Ни читать, ни писать |
| Методы | Вызывать static-методы | Вызывать методы экземпляра |
| Ссылка this | Только слева от = при присваивании полю | Передавать как значение, возвращать |
| Внутренние классы | Обращаться к членам внешнего (outer) класса | Создавать нестатические inner-классы |
| Локальные вычисления | Любые: проверки, циклы, throw | — |
Сценарий 1. Валидация аргументов
Вернёмся к классу HeavyBox. Нужно проверить, что ширина больше нуля: если она меньше либо равна нулю, создавать объект бессмысленно — выбрасываем исключение до вызова конструктора родителя.
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;
} Сценарий 2. Подготовка аргументов
Допустим, размеры нужно удвоить перед передачей в конструктор суперкласса:
public HeavyBox(int width, int height, int depth, int weight) {
width *= 2;
height *= 2;
depth *= 2;
super(width, height, depth);
this.weight = weight;
} Сценарий 3. Переиспользование одного объекта в аргументах
Пусть в классе Box есть конструктор, принимающий два объекта Point:
public class Box {
// ...
public Box(Point p1, Point p2) {
}
// ...
} Если перед super() нельзя писать код, придётся создать два разных объекта (или принимать их извне):
public HeavyBox() {
super(new Point(), new Point());
} С гибким телом конструктора достаточно создать один объект и передать его дважды:
public HeavyBox() {
Point p = new Point();
super(p, p);
} 4. Обращение к полям и методам суперкласса
Вторая форма — super.member — даёт доступ к полю или методу суперкласса из класса-наследника. В большинстве случаев super не нужен: унаследованные члены и так видны по имени. Он необходим только тогда, когда имя перекрыто в подклассе.
В классе C объявлено поле i типа int. В его наследнике D тоже объявлено поле i, но типа String. (Сразу предупредим: на практике так делать не стоит — пример нужен только для иллюстрации.) Из класса D по имени i доступно только поле String i, которое скрывает int i. Чтобы добраться до int i, нужен super.i.
С методами похоже. В обоих классах определён метод print(). Чтобы из D вызвать реализацию класса C, пишем super.print().
public class C {
public int i;
public void print() {
System.out.println("C.i = " + i);
}
}
public class D extends C {
public String i;
public D(String a, int b) {
i = a;
super.i = b;
}
public void print() {
System.out.println("D.i = " + i);
super.print();
}
}
public class UseSuperExample {
public static void main(String[] args) {
D d = new D("someString", 2);
d.print();
System.out.println(d.i);
}
} Вывод в консоль:
D.i = someString
C.i = 2
someString Обратите внимание на разницу между полями и методами. Поля скрываются (hiding) и разрешаются по типу ссылки, а методы переопределяются (overriding) и разрешаются по реальному типу объекта. Именно поэтому super.print() — единственный способ вызвать «родительскую» версию метода из переопределённой: обычный вызов print() внутри D привёл бы к бесконечной рекурсии.
Запомните
super — это не объект и не ссылка, которую можно сохранить в переменную: написать Object o = super; нельзя. Это специальная форма доступа, которую компилятор разворачивает в невиртуальный вызов (инструкция invokespecial в байт-коде) — именно поэтому super.print() не приводит к рекурсии.
5. super, super(), this и this(): в чём разница
Четыре похожие конструкции, которые часто путают на собеседовании:
| Конструкция | Что делает | Где можно использовать | Типичный пример |
|---|---|---|---|
super(...) | Вызывает конструктор суперкласса | Только в конструкторе, один раз | super(width, height, depth); |
this(...) | Вызывает другой конструктор этого же класса | Только в конструкторе, один раз | this(0, 0, 0, 0); |
super.member | Обращается к скрытому полю или переопределённому методу предка | В любом методе экземпляра и в конструкторе | super.print(); |
this.member | Обращается к полю или методу текущего объекта | В любом методе экземпляра и в конструкторе | this.weight = weight; |
Ключевое ограничение: super(...) и this(...) взаимоисключающие — в одном конструкторе может быть только один из них. Если конструктор начинается с this(...), то вызов конструктора суперкласса произойдёт в том конструкторе, которому мы делегировали работу.
6. Где чаще всего спотыкаются
- Забыли конструктор без параметров у родителя. Добавили в суперкласс конструктор с аргументами — и все наследники, полагавшиеся на неявный
super(), перестали компилироваться. superв статическом контексте. Вstatic-методе и в статическом блокеsuper(как иthis) недоступен: объекта ещё нет.- Вызов переопределяемого метода из конструктора. Если конструктор суперкласса вызывает метод, переопределённый в наследнике, тот отработает до инициализации полей наследника и увидит
nullили0. - Попытка написать
super.super.method(). Такой синтаксис в Java запрещён: «перепрыгнуть» через уровень иерархии нельзя. - Порядок вывода в логах. Тела конструкторов выполняются от корня иерархии к листу, а не наоборот — классический вопрос на собеседовании.
Часто задаваемые вопросы
Можно ли вызвать super() и this() в одном конструкторе?
Нет. В конструкторе допускается ровно один явный вызов — либо super(...), либо this(...). Если написан this(...), компилятор не подставляет неявный super(): конструктор суперкласса будет вызван в том конструкторе, которому вы делегировали работу. В итоге в цепочке всё равно происходит ровно один вызов super(...).
Почему не работает super.super.method()?
Такой синтаксис запрещён спецификацией Java. Пропустить уровень иерархии нельзя: класс отвечает только за контракт со своим непосредственным родителем, и возможность обойти его реализацию нарушила бы инкапсуляцию. Если доступ к методу «через уровень» действительно нужен, добавьте в промежуточном классе метод-мост, который вызывает super.method().
Как вызвать default-метод интерфейса, если класс его переопределил?
Через квалифицированную форму ИмяИнтерфейса.super.method(), например Walkable.super.move(). Это работает только для интерфейсов, которые класс реализует напрямую, и особенно полезно, когда два интерфейса дают конфликтующие реализации одного default-метода.
Можно ли использовать super в статическом методе?
Нет. Ключевые слова super и this связаны с конкретным экземпляром, а статический метод выполняется без объекта, поэтому такой код не скомпилируется. Чтобы обратиться к статическому члену родителя, используйте имя класса: Box.someStaticMethod().
Что означает ошибка there is no default constructor available?
Компилятор пытался подставить неявный super() в конструктор наследника, но у суперкласса нет конструктора без параметров. Так бывает, когда в родительском классе объявлен хотя бы один конструктор с аргументами: конструктор по умолчанию тогда не генерируется. Решение — либо добавить в родителя конструктор без параметров, либо явно вызвать существующий конструктор через super(...).
Итоги
superссылается на непосредственный суперкласс:super(...)вызывает его конструктор,super.memberдаёт доступ к его полю или методу.- Если явного вызова нет, компилятор сам подставляет
super()первой строкой — значит, у родителя должен быть конструктор без параметров. - Тела конструкторов выполняются от корня иерархии к текущему классу, а не наоборот.
super.memberнужен только тогда, когда имя скрыто или переопределено в наследнике.- Код перед
super()разрешён начиная с Java 25 (JEP 513); на Java 17 и 21 вызов конструктора суперкласса по-прежнему должен быть первым оператором.
Презентацию с видео можно скачать на Patreon.
Видео объяснение
Предпочитаете видеоформат? Посмотрите этот урок с примерами и объяснениями.
Комментарии