super(), и он должен стоять в конструкторе первым.
Кратко
- При создании объекта сначала вызывается конструктор базового класса, потом конструктор самого класса.
- Наследование задается словом
extends: класс-потомок получает доступ к полям и методам базового класса. super()вызывает конструктор родителя и должен стоять в конструкторе первым.- Общие поля выносят в базовый класс, чтобы не дублировать их в потомках.
- Порядок инициализации: статические поля (сначала базовый класс, потом потомок), затем нестатические поля и конструкторы в том же порядке.
Что такое базовый класс
Дело в том, что в Java несколько разных классов могут иметь общее происхождение.Animal (животное):
public class Animal {
String name;
int age;
}
Мы можем создать для него, например, 2 класса-потомка, Cat и Dog. Это делается с использованием ключевого слова extends.
public class Cat extends Animal {
}
public class Dog extends Animal {
}
Это может нам пригодиться в будущем. Например, если будет задача ловить мышей, создадим в программе объект Cat. Если задача бегать за палочкой, тут мы используем объект Dog. А если будем создавать программу, симулирующую ветеринарную клинику, она будет работать с классом Animal (чтобы уметь лечить и кошек, и собак).
Порядок вызова конструкторов
Очень важно запомнить на будущее, что при создании объекта в первую очередь вызывается конструктор его базового класса, а только потом конструктор самого класса, объект которого мы создаем. То есть при создании объектаCat сначала отработает конструктор класса Animal, а только потом конструктор Cat.
Чтобы убедиться в этом, добавим в конструкторы Cat и Animal вывод в консоль.
public class Animal {
public Animal() {
System.out.println("Отработал конструктор Animal");
}
}
public class Cat extends Animal {
public Cat() {
System.out.println("Отработал конструктор Cat!");
}
public static void main(String[] args) {
Cat cat = new Cat();
}
}
Вывод в консоль:
Отработал конструктор Animal
Отработал конструктор Cat!
Действительно, все так и работает!
Для чего это нужно? Например, чтобы не дублировать общие поля двух классов. Например, у каждого животного есть сердце и мозг, но не у каждого есть хвост.
Мы можем объявить общие для всех животных поля brain и heart в родительском классе Animal, а поле tail в подклассе Cat.
Теперь мы создадим конструктор для класса Cat, куда передадим все 3 поля.
public class Cat extends Animal {
String tail;
public Cat(String brain, String heart, String tail) {
this.brain = brain;
this.heart = heart;
this.tail = tail;
}
public static void main(String[] args) {
Cat cat = new Cat("Мозг", "Сердце", "Хвост");
}
}
Обрати внимание: конструктор успешно работает, хотя в классе Cat нет полей brain и heart.
Эти поля "подтянулись" из базового класса Animal. У класса-наследника есть доступ к полям базового класса, поэтому в нашем классе Cat они видны. Поэтому нам не нужно в классе Cat дублировать эти поля, мы можем взять их из класса Animal.
Ключевое слово super()
Более того, мы можем явно вызвать конструктор базового класса в конструкторе класса-потомка. Базовый класс еще называют "суперклассом", поэтому в Java для его обозначения используется ключевое словоsuper.
В предыдущем примере
public Cat(String brain, String heart, String tail) {
this.brain = brain;
this.heart = heart;
this.tail = tail;
}
Мы отдельно присваивали каждое поле, которое есть в нашем родительском классе. На самом деле этого можно не делать.
Достаточно вызвать конструктор родительского класса и передать ему нужные параметры:
public class Animal {
String brain;
String heart;
public Animal(String brain, String heart) {
this.brain = brain;
this.heart = heart;
}
}
public class Cat extends Animal {
String tail;
public Cat(String brain, String heart, String tail) {
super(brain, heart);
this.tail = tail;
}
public static void main(String[] args) {
Cat cat = new Cat("Мозг", "Сердце", "Хвост");
}
}
В конструкторе Cat мы вызвали конструктор Animal и передали в него два поля. Нам осталось явно проинициализировать только одно поле, tail, которого в Animal нет.
Помнишь, мы говорили о том, что при создании объекта в первую очередь вызывается конструктор класса-родителя? Так вот, именно поэтому слово super() всегда должно стоять в конструкторе первым!
Иначе логика работы конструкторов будет нарушена и программа выдаст ошибку.
public class Cat extends Animal {
String tail;
public Cat(String brain, String heart, String tail) {
this.tail = tail;
super(brain, heart);//ошибка!
}
public static void main(String[] args) {
Cat cat = new Cat("Мозг", "Сердце", "Хвост");
}
}
Компилятор знает, что при создании объекта класса-потомка сначала вызывается конструктор базового класса. И если ты попытаешься вручную изменить это поведение, он не позволит этого сделать.
Подробнее о ключевом слове super можно прочитать в документации Oracle (Using the Keyword super).
Порядок инициализации при создании объекта
Выше мы с тобой рассмотрели пример с базовым и родительским классом,Animal и Cat.
Теперь на примере этих двух классов мы рассмотрим процесс создания объекта и инициализации переменных.
Мы знаем, что переменные бывают статическими и переменными экземпляра (нестатическими). Также мы знаем, что в базовом классе Animal есть свои переменные, а в классе-потомке Cat свои.
Добавим к классу Animal и Cat по одной статической переменной для наглядности.
Переменная animalCount в классе Animal будет означать общее число видов животных на земле, а переменная catsCount число видов семейства кошачьих.
Кроме того, присвоим всем нестатическим переменным у обоих классов стартовые значения (которое потом изменится в конструкторе).
public class Animal {
String brain = "Изначальное значение brain в классе Animal";
String heart = "Изначальное значение heart в классе Animal";
public static int animalCount = 7700000;
public Animal(String brain, String heart) {
System.out.println("Выполняется конструктор базового класса Animal");
System.out.println("Были ли уже проинициализированы переменные класса Animal?");
System.out.println("Текущее значение статической переменной animalCount = " + animalCount);
System.out.println("Текущее значение brain в классе Animal = " + this.brain);
System.out.println("Текущее значение heart в классе Animal = " + this.heart);
System.out.println("Были ли уже проинициализированы переменные класса Cat?");
System.out.println("Текущее значение статической переменной catsCount = " + Cat.catsCount);
this.brain = brain;
this.heart = heart;
System.out.println("Конструктор базового класса Animal завершил работу!");
System.out.println("Текущее значение brain = " + this.brain);
System.out.println("Текущее значение heart = " + this.heart);
}
}
public class Cat extends Animal {
String tail = "Изначальное значение tail в классе Cat";
static int catsCount = 37;
public Cat(String brain, String heart, String tail) {
super(brain, heart);
System.out.println("Конструктор класса Cat начал работу (конструктор Animal уже был выполнен)");
System.out.println("Текущее значение статической переменной catsCount = " + catsCount);
System.out.println("Текущее значение tail = " + this.tail);
this.tail = tail;
System.out.println("Текущее значение tail = " + this.tail);
}
public static void main(String[] args) {
Cat cat = new Cat("Мозг", "Сердце", "Хвост");
}
}
Итак, мы создаем новый объект класса Cat, унаследованного от Animal. Добавим в нашу программу подробный консольный вывод, чтобы посмотреть что и в каком порядке будет происходить.
Вот что будет выведено в консоль в результате создания объекта Cat:
Выполняется конструктор базового класса Animal
Были ли уже проинициализированы переменные класса Animal?
Текущее значение статической переменной animalCount = 7700000
Текущее значение brain в классе Animal = Изначальное значение brain в классе Animal
Текущее значение heart в классе Animal = Изначальное значение heart в классе Animal
Были ли уже проинициализированы переменные класса Cat?
Текущее значение статической переменной catsCount = 37
Конструктор базового класса Animal завершил работу!
Текущее значение brain = Мозг
Текущее значение heart = Сердце
Конструктор класса Cat начал работу (конструктор Animal уже был выполнен)
Текущее значение статической переменной catsCount = 37
Текущее значение tail = Изначальное значение tail в классе Cat
Текущее значение tail = Хвост
Итак, теперь мы наглядно видим в каком порядке происходит инициализация переменных и вызов конструкторов при создании нового объекта:
Инициализируются статические переменные базового класса (
Animal). В нашем случае переменнойanimalCountклассаAnimalприсваивается значение 7700000.Инициализируются статические переменные класса-потомка (
Cat). Обрати внимание: мы все еще внутри конструктораAnimal, а в консоли уже написано:Выполняется конструктор базового класса Animal Были ли уже проинициализированы переменные класса Animal? Текущее значение статической переменной animalCount = 7700000 Текущее значение brain в классе Animal = Изначальное значение brain в классе Animal Текущее значение heart в классе Animal = Изначальное значение heart в классе Animal Были ли уже проинициализированы переменные класса Cat? Текущее значение статической переменной catsCount = 37Дальше инициализируются нестатические переменные базового класса. Мы специально присвоили им первоначальные значения, которые потом в конструкторе меняются на новые. Конструктор
Animalеще не отработал до конца, но первоначальные значенияbrainиheartуже присвоены:Выполняется конструктор базового класса Animal Были ли уже проинициализированы переменные класса Animal? Текущее значение статической переменной animalCount = 7700000 Текущее значение brain в классе Animal = Изначальное значение brain в классе Animal Текущее значение heart в классе Animal = Изначальное значение heart в классе AnimalНачинает работу конструктор базового класса.
В том, что этот этап идет только четвертым по счету, мы уже убедились: в первых трех пунктах на момент начала работы конструктора
Animalмногим переменным уже присвоены значения.Инициализация нестатических полей дочернего класса (
Cat).Она происходит раньше, чем конструктор
Catначинает работу.На момент, когда он начал работу, у переменной
tailуже было значение:Конструктор класса Cat начал работу (конструктор Animal уже был выполнен) Текущее значение статической переменной catsCount = 37 Текущее значение tail = Изначальное значение tail в классе CatВызывается конструктор класса потомка
Cat
| Шаг | Что инициализируется |
|---|---|
| 1 | статические поля базового класса (Animal) |
| 2 | статические поля класса-потомка (Cat) |
| 3 | нестатические поля базового класса |
| 4 | конструктор базового класса |
| 5 | нестатические поля класса-потомка |
| 6 | конструктор класса-потомка |
Вопросы и ответы
Что такое базовый класс в Java?
Базовый (родительский) класс это класс, от которого наследуются другие классы черезextends. Его также называют суперклассом. Классы-потомки получают доступ к его полям и методам.
В каком порядке вызываются конструкторы при наследовании?
Сначала вызывается конструктор базового класса, потом конструктор класса-потомка. Например, при созданииCat сначала отработает конструктор Animal, затем конструктор Cat.
Что делает ключевое слово super()?
super() вызывает конструктор базового класса и передает ему нужные параметры. Оно должно стоять первой строкой в конструкторе потомка.Почему super() должен быть первым в конструкторе?
Потому что конструктор базового класса всегда выполняется раньше конструктора потомка. Если поставить super() не первым, компилятор выдаст ошибку.Какой порядок инициализации переменных при создании объекта?
Сначала статические поля базового класса, потом статические поля потомка, затем нестатические поля базового класса и его конструктор, потом нестатические поля потомка и его конструктор.Зачем нужны базовые классы?
Чтобы не дублировать общие поля и методы. Общее выносят в базовый класс, а специфичное оставляют в потомках. Это уменьшает количество кода.Читайте также
- Отношения между классами: наследование, композиция и агрегирование.
- Принципы ООП: наследование как один из четырех принципов.
- Оператор instanceof: почему объект принадлежит и своему классу, и всем его предкам.