Урок 10. Наследование и интерфейсы
Наследование позволяет создать новый класс на основе существующего: новый получает все поля и методы старого и может добавить свои или заменить унаследованные. Старый класс называют родительским (суперклассом), новый — наследником (подклассом).
Наследование нужно, когда несколько классов похожи: собака и кошка — животные, у обеих есть имя и обе умеют подавать голос, но голос у них разный.
extends и super
public class Main {
public static void main(String[] args) {
Animal[] animals = {new Dog("Шарик"), new Cat("Мурка"), new Animal("Ёжик")};
for (Animal a : animals) {
a.speak();
}
}
}
class Animal {
protected String name;
Animal(String name) {
this.name = name;
}
void speak() {
System.out.println(name + " издаёт звук");
}
}
class Dog extends Animal {
Dog(String name) {
super(name);
}
@Override
void speak() {
System.out.println(name + ": Гав!");
}
}
class Cat extends Animal {
Cat(String name) {
super(name);
}
@Override
void speak() {
System.out.println(name + ": Мяу!");
}
}Шарик: Гав!
Мурка: Мяу!
Ёжик издаёт звукclass Dog extends Animal—DogнаследуетAnimal: у собаки есть полеnameи методspeak, хотя в самом классеDogполе не объявлено.super(name)вызывает конструктор родителя. Если у родителя нет конструктора без параметров, этот вызов обязателен; его ставят в начале конструктора наследника.@Override— методspeakпереопределён: у собаки он свой. Пометка не обязательна, но с ней компилятор проверит, что такой метод у родителя действительно есть.protected— поле видно в классе, его наследниках и в том же пакете.
Вызвать версию метода из родителя можно через super.speak() — например, чтобы дополнить её, а не заменить целиком.
Полиморфизм
Обратите внимание на массив Animal[]: в нём лежат и собаки, и кошки. Переменная типа родителя может ссылаться на объект наследника — ведь собака является животным. А при вызове a.speak() выполняется метод того класса, каким объект был создан. Это и есть полиморфизм: один вызов — разное поведение.
Благодаря ему код, который работает с Animal, не нужно менять, когда появляется новое животное: достаточно написать ещё один класс-наследник.
Проверить, какого класса объект, позволяет instanceof: if (a instanceof Dog). С Java 16 можно сразу получить переменную нужного типа: if (a instanceof Dog d) { … } — внутри блока d уже имеет тип Dog.
Ограничения
- У класса может быть только один родитель:
class A extends B, Cне скомпилируется. - Все классы, у которых родитель не указан, наследуют класс
Object. От него у любого объекта есть методыtoString,equalsиhashCode. - Класс с пометкой
finalнаследовать нельзя. Так объявлен, например,String.
Абстрактный класс
Иногда родитель — понятие слишком общее, чтобы создавать его объекты. «Просто фигуру» не нарисовать, нарисовать можно круг или квадрат. Такой класс объявляют абстрактным:
abstract class Figure {
abstract double area();
void describe() {
System.out.println("Площадь: " + area());
}
}new Figure()не скомпилируется — объект абстрактного класса создать нельзя.- Метод
area()без тела помеченabstract: каждый наследник обязан его написать, иначе сам станет абстрактным. - Обычные методы и поля в абстрактном классе тоже бывают —
describeвызываетarea()того наследника, у которого её вызвали.
Интерфейс
Интерфейс — список методов, которые класс обещает реализовать. Он описывает, что объект умеет, а не что он такое. Класс подключает интерфейс словом implements:
public class Main {
public static void main(String[] args) {
Shape[] shapes = {new Rectangle(3, 4), new Square(5)};
for (Shape s : shapes) {
System.out.println(s.name() + ": площадь " + s.area());
}
}
}
interface Shape {
double area();
String name();
}
class Rectangle implements Shape {
private final double width;
private final double height;
Rectangle(double width, double height) {
this.width = width;
this.height = height;
}
@Override
public double area() {
return width * height;
}
@Override
public String name() {
return "Прямоугольник";
}
}
class Square extends Rectangle {
Square(double side) {
super(side, side);
}
@Override
public String name() {
return "Квадрат";
}
}Прямоугольник: площадь 12.0
Квадрат: площадь 25.0- Методы интерфейса по умолчанию публичные и абстрактные, поэтому в классе их реализация обязана быть
public. Безpublicкомпилятор сообщитattempting to assign weaker access privileges; was public. SquareнаследуетRectangle, а значит, тоже реализуетShapeи берётarea()у родителя.- Класс может реализовать несколько интерфейсов сразу:
class Duck extends Bird implements Swimmer, Flyer.
Многие стандартные возможности Java построены на интерфейсах. Например, List — интерфейс, а ArrayList — один из классов, которые его реализуют; поэтому пишут List<String> list = new ArrayList<>();. Интерфейс Comparable говорит, что объекты можно сравнивать и сортировать.
Абстрактный класс или интерфейс
- Абстрактный класс — когда у наследников есть общие поля и общий код, и они действительно разновидности одного понятия («круг — это фигура»).
- Интерфейс — когда нужно описать умение, общее для разных классов («умеет сравниваться», «умеет сохраняться в файл»). Интерфейсов у класса может быть много, родитель — один.
Коротко
class B extends A—Bполучает поля и методыA; родитель у класса один.super(…)вызывает конструктор родителя,super.метод()— его версию метода.@Overrideотмечает переопределённый метод; вызывается версия класса, каким создан объект, — полиморфизм.- Абстрактный класс нельзя создать через
new, его абстрактные методы реализуют наследники. - Интерфейс подключают через
implements, его методы в классе —public; интерфейсов может быть несколько.