Наследование
Наследование позволяет построить новый класс на основе существующего. Новый класс получает поля и методы родителя и может добавить свои или изменить унаследованные.
extends
Класс-наследник объявляют с ключевым словом extends. Родителя называют суперклассом, а наследника — подклассом:
class Animal {
String name;
String describe() {
return name + " — животное";
}
}
class Dog extends Animal {
String bark() {
return name + ": Гав!";
}
}
void main() {
Dog dog = new Dog();
dog.name = "Рекс";
IO.println(dog.describe());
IO.println(dog.bark());
}Результат:
Рекс — животное
Рекс: Гав!У Dog нет своего поля name и метода describe() — они пришли от Animal.
Конструктор и super(...)
Конструкторы не наследуются. Конструктор подкласса вызывает конструктор родителя через super(...), передавая ему нужные данные:
class Animal {
protected String name;
Animal(String name) {
this.name = name;
}
}
class Cat extends Animal {
Cat(String name) {
super(name);
}
String meow() {
return name + ": Мяу";
}
}
void main() {
IO.println(new Cat("Мурка").meow());
}Результат:
Мурка: МяуМодификатор protected открывает поле для подклассов и для классов того же пакета, но не для всего остального кода. Пакеты разберём в конце главы.
Переопределение методов
Подкласс может объявить метод с той же сигнатурой, что у родителя, — тогда вызывается его версия. Это называется переопределением. Аннотация @Override просит компилятор проверить, что такой метод у родителя действительно есть:
class Animal {
String sound() {
return "...";
}
}
class Dog extends Animal {
@Override
String sound() {
return "Гав";
}
}
void main() {
IO.println(new Animal().sound());
IO.println(new Dog().sound());
}Результат:
...
ГавВызов версии родителя
Через super.метод() подкласс вызывает родительскую версию метода и дополняет её, а не пишет всё заново:
class Product {
String label() {
return "Товар";
}
}
class Book extends Product {
@Override
String label() {
return super.label() + " / Книга";
}
}
void main() {
IO.println(new Book().label());
}Результат:
Товар / КнигаОдин родитель и класс Object
В Java у класса может быть только один прямой родитель. Если extends не указан, родителем становится класс Object — корень всей иерархии. Поэтому у любого объекта есть методы toString() и equals(): они унаследованы от Object. Класс с модификатором final запрещает наследование от себя — так устроен, например, String.
Частая ошибка
Если у родителя есть только конструктор с параметрами, подкласс обязан вызвать его через super(...). Без этого Java пытается вызвать конструктор без параметров, которого нет:
class Animal {
String name;
Animal(String name) {
this.name = name;
}
}
class Dog extends Animal {
Dog() {
IO.println("Создаём собаку");
}
}
void main() {
new Dog();
}Результат:
Main.java:10: error: constructor Animal in class Main.Animal cannot be applied to given types;
Dog() {
^Добавьте super("Рекс"); или передайте имя через параметр конструктора Dog.