
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
.
Более того, мы можем явно вызвать конструктор базового класса в конструкторе класса-потомка. Базовый класс еще называют “суперклассом”, поэтому в 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("Мозг", "Сердце", "Хвост");
}
}
Компилятор знает, что при создании объекта класса-потомка сначала вызывается конструктор базового класса. И если ты попытаешься вручную изменить это поведение - он не позволит этого сделать.
Процесс создания объекта.
Выше мы с тобой рассмотрели пример с базовым и родительским классом —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
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ