Полиморфизм
Полиморфизм — это способность одного и того же кода работать с объектами разных классов. Код обращается к объекту как к «животному», а какой именно звук прозвучит, решает сам объект.
Переменная типа родителя
В переменную типа суперкласса можно положить объект любого подкласса: собака — это животное. Но вызывается при этом версия метода самого объекта:
class Animal {
String sound() {
return "...";
}
}
class Dog extends Animal {
@Override
String sound() {
return "Гав";
}
}
void main() {
Animal animal = new Dog();
IO.println(animal.sound());
}Результат:
ГавТип переменной — Animal, а объект в ней — Dog. Java выбирает метод по настоящему объекту во время выполнения программы.
Один цикл для разных объектов
Благодаря этому разные объекты можно сложить в один массив или список и обработать одинаково:
class Animal {
String sound() {
return "...";
}
}
class Dog extends Animal {
@Override
String sound() {
return "Гав";
}
}
class Cat extends Animal {
@Override
String sound() {
return "Мяу";
}
}
void main() {
Animal[] animals = { new Dog(), new Cat(), new Animal() };
for (Animal a : animals) {
IO.println(a.sound());
}
}Результат:
Гав
Мяу
...Циклу не нужно знать, кто перед ним — собака или кошка.
Параметр типа родителя
Метод, который принимает суперкласс, подходит для всех подклассов, включая те, что появятся позже. Чтобы добавить новый вид фигуры, не придётся менять printArea():
class Shape {
double area() {
return 0;
}
}
class Square extends Shape {
double side;
Square(double side) {
this.side = side;
}
@Override
double area() {
return side * side;
}
}
class Circle extends Shape {
double radius;
Circle(double radius) {
this.radius = radius;
}
@Override
double area() {
return Math.PI * radius * radius;
}
}
void printArea(Shape shape) {
IO.println("Площадь: " + Math.round(shape.area()));
}
void main() {
printArea(new Square(3));
printArea(new Circle(2));
}Результат:
Площадь: 9
Площадь: 13Проверка типа: instanceof
Оператор instanceof проверяет, к какому классу относится объект. Если после имени класса указать переменную, Java сразу приведёт объект к этому типу:
class Animal {}
class Dog extends Animal {
String fetch() {
return "Принёс палку";
}
}
void main() {
Animal animal = new Dog();
if (animal instanceof Dog dog) {
IO.println(dog.fetch());
}
}Результат:
Принёс палкуПеременная dog существует только внутри if, когда проверка прошла. Проверки типа стоит использовать редко: чаще правильнее добавить общий метод в суперкласс.
Почему это удобно
Код, который работает с суперклассом, не меняется при добавлении новых подклассов. Новый класс просто переопределяет нужные методы, и вся программа сразу умеет с ним работать. На этом держатся интерфейсы и абстрактные классы из следующих уроков.
Частая ошибка
Через переменную типа родителя видны только методы родителя. Метод, который есть лишь у подкласса, так вызвать нельзя, даже если в переменной лежит нужный объект:
class Animal {}
class Dog extends Animal {
String fetch() {
return "Принёс палку";
}
}
void main() {
Animal animal = new Dog();
IO.println(animal.fetch());
}Результат:
Main.java:11: error: cannot find symbol
IO.println(animal.fetch());
^Компилятор смотрит на тип переменной, а не на объект. Используйте переменную типа Dog или проверку instanceof.