Polymorphism
Polymorphism is the ability of the same code to work with objects of different classes. The code treats an object as "an animal", and the object itself decides which sound to make.
A variable of the parent type
A variable of a superclass type can hold an object of any subclass: a dog is an animal. Yet the version of the method that runs is the object's own:
class Animal {
String sound() {
return "...";
}
}
class Dog extends Animal {
@Override
String sound() {
return "Woof";
}
}
void main() {
Animal animal = new Dog();
IO.println(animal.sound());
}Output:
WoofThe variable's type is Animal, and the object in it is a Dog. Java chooses the method by the real object while the program runs.
One loop for different objects
Thanks to this, different objects can be put into one array or list and handled the same way:
class Animal {
String sound() {
return "...";
}
}
class Dog extends Animal {
@Override
String sound() {
return "Woof";
}
}
class Cat extends Animal {
@Override
String sound() {
return "Meow";
}
}
void main() {
Animal[] animals = { new Dog(), new Cat(), new Animal() };
for (Animal a : animals) {
IO.println(a.sound());
}
}Output:
Woof
Meow
...The loop does not need to know whether it has a dog or a cat.
A parameter of the parent type
A method that accepts a superclass works for all subclasses, including those that will appear later. Adding a new kind of shape will not require changing 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("Area: " + Math.round(shape.area()));
}
void main() {
printArea(new Square(3));
printArea(new Circle(2));
}Output:
Area: 9
Area: 13Checking the type: instanceof
The instanceof operator checks which class an object belongs to. If you put a variable after the class name, Java casts the object to that type right away:
class Animal {}
class Dog extends Animal {
String fetch() {
return "Fetched the stick";
}
}
void main() {
Animal animal = new Dog();
if (animal instanceof Dog dog) {
IO.println(dog.fetch());
}
}Output:
Fetched the stickThe variable dog exists only inside the if, when the check has passed. Type checks should be rare: more often the right move is to add a shared method to the superclass.
Why this is convenient
Code that works with a superclass does not change when new subclasses are added. A new class simply overrides the methods it needs, and the whole program can work with it straight away. Interfaces and abstract classes from the next lessons are built on this.
Common mistake
Through a variable of the parent type only the parent's methods are visible. A method that exists only in the subclass cannot be called this way, even if the variable holds the right object:
class Animal {}
class Dog extends Animal {
String fetch() {
return "Fetched the stick";
}
}
void main() {
Animal animal = new Dog();
IO.println(animal.fetch());
}Output:
Main.java:11: error: cannot find symbol
IO.println(animal.fetch());
^The compiler looks at the variable's type, not at the object. Use a variable of type Dog or an instanceof check.