Наследование
Чему вы научитесь:
- объявлять класс-наследник через
extendsи вызывать конструктор родителя черезsuper(…); - предсказывать, чей метод выполнит вызов
a.sound(), и отличать переопределение от перегрузки; - объяснять, зачем классу и методу слова
abstractиfinal; - приводить ссылку к типу наследника и проверять тип объекта через
instanceof.
Представьте телевизор и пульт к нему. На пульте две кнопки: «Вкл» и «Громкость». Через год фирма выпускает новую модель. Она умеет всё, что умела прежняя, и вдобавок записывает передачи. Новую модель не проектируют с нуля: берут прежнюю и добавляют одну функцию.
Поэтому старый пульт подходит и к новому телевизору. Каждую его кнопку новая модель понимает: всё прежнее в ней осталось.
В Java так устроено наследование: новый класс получает поля и методы готового класса и добавляет к ним свои.
Зачем это нужно? Допустим, в программе есть собаки и кошки, и те и другие едят. Без наследования метод eat пришлось бы написать в обоих классах, а найденную в нём ошибку — исправлять дважды. С наследованием общее пишут один раз, в классе Animal:
public class Main {
public static void main(String[] args) {
Dog d = new Dog();
d.eat(); // eats
d.bark(); // Woof
}
}
class Animal {
void eat() { System.out.println("eats"); }
}
class Dog extends Animal {
void bark() { System.out.println("Woof"); }
}Давайте разберём программу по шагам:
- Класс
Animalописывает то, что умеет любое животное: методeat. Тело метода здесь записано в одну строку, чтобы пример был короче. - Запись
class Dog extends Animalобъявляет классDogнаследникомAnimal. Словоextendsзначит «расширяет». - В самом
Dogнаписан только методbark. new Dog()строит собаку, иd.eat()печатаетeats: этот метод собака получила отAnimal.d.bark()печатаетWoof— это собственный методDog.
Родитель и наследник
Наследование — способ объявить новый класс на основе существующего: новый класс получает поля и методы старого. Класс, от которого наследуют, называют суперклассом или родителем, а новый класс — подклассом или наследником. Здесь Animal — суперкласс, а Dog — подкласс.
Наследование описывает отношение «является»: собака является животным. Поэтому всё, что умеет Animal, умеет и Dog. Обратное неверно: у Animal нет метода bark.
Что наследник получает, а что нет:
- поля и методы родителя он получает: у объекта
Dogк ним обращаются, как к собственным; private-поля и методы родителя видны только внутри родителя: если обратиться изDogкprivate-полюnameклассаAnimal, компилятор ответитname has private access in Animal;- конструкторы не наследуются: у каждого класса свои.
Конструктор родителя: super(…)
Собака — это животное плюс своё. Поэтому, когда строится объект Dog, сначала должна отработать часть, которую описывает Animal. Если у Animal есть конструктор с параметром, наследник вызывает его словом super:
public class Main {
public static void main(String[] args) {
Dog d = new Dog("Rex");
System.out.println(d.name); // Rex
}
}
class Animal {
String name;
Animal(String name) { this.name = name; }
}
class Dog extends Animal {
Dog(String name) { super(name); }
}Разберём по шагам:
new Dog("Rex")вызывает конструкторDogс аргументом"Rex".- Первая инструкция этого конструктора,
super(name), вызывает конструкторAnimalи передаёт ему"Rex". - Конструктор
Animalкладёт имя в полеname, и управление возвращается в конструкторDog. d.nameпечатаетRex: поле объявлено вAnimal, а заполнено черезsuper.
Правила у super(…) такие:
- его пишут первой инструкцией конструктора, как и
this(…); Java 25 разрешает перед ним некоторые инструкции, но код курса компилируется и на Java 21; - если
super(…)не написать, компилятор сам вставит в начало конструктораsuper()— вызов конструктора родителя без аргументов; - поэтому конструктор родителя всегда заканчивает работу раньше, чем выполняется код наследника после
super(…).
О порядке, в котором выполняются конструкторы, часто спрашивают на собеседовании. Ответ: сверху вниз, от самого дальнего родителя к наследнику.
Один родитель и класс Object
У класса Java ровно один родитель: запись class Dog extends Animal, Pet не скомпилируется. Зато цепочка может быть длинной: класс Puppy extends Dog получит всё и от Dog, и от Animal.
А если extends не написать, родителем станет класс Object. Он стоит в корне всех классов: любой класс — его наследник, напрямую или через цепочку. Поэтому у любого объекта есть методы Object, например toString.
Здесь картинка с телевизорами неточна. Новую модель инженеры могут собрать из узлов двух прежних, а у класса Java родитель всегда один.
Частая ошибка
Родитель требует аргумент, а наследник не вызвал super(…):
class Animal {
String name;
Animal(String name) { this.name = name; }
}
class Dog extends Animal {
Dog(String name) { } // ✗
}error: constructor Animal in class Animal cannot be applied to given types;Программа не компилируется. Раз super(…) не написан, компилятор вставил super() без аргументов, а у Animal такого конструктора нет — есть только конструктор с String. Сообщение то же, что и при вызове new без нужных аргументов. Вызовите конструктор родителя сами: Dog(String name) { super(name); }.
Главное
class Dog extends Animalобъявляет наследника:Dogполучает поля и методыAnimalи добавляет свои.private-поля и методы родителя наследнику не видны, а конструкторы не наследуются.super(…)вызывает конструктор родителя; без него компилятор вставляетsuper(), и конструктор родителя отрабатывает раньше кода наследника.- У класса ровно один родитель, а в корне всех классов стоит
Object.