Run-time polymorphism in action
A superclass reference can refer to a subclass object. When an overridden method is called, Java runs the version belonging to the actual object, not the reference type.
class Shape {
double area() { return 0; }
}
class Circle extends Shape {
double r;
Circle(double r) { this.r = r; }
@Override double area() { return Math.PI * r * r; }
}
class Rectangle extends Shape {
double w, h;
Rectangle(double w, double h) { this.w = w; this.h = h; }
@Override double area() { return w * h; }
}
public class Main {
public static void main(String[] args) {
Shape[] shapes = { new Circle(3), new Rectangle(4, 5), new Circle(1) };
for (Shape s : shapes) {
System.out.println(s.area()); // correct version each time
}
}
}One loop handles every shape, including shapes added in the future. This is the main power of polymorphism: code that works with the general type and does not need to change when new subclasses are added.
Walking through the loop
shapes is an array of Shape references, but the objects are a Circle, a Rectangle and another Circle.
- In the first pass,
srefers to the Circle with radius 3.s.area()runs Circle'sarea(): π × 3 × 3 ≈ 28.27. - In the second pass,
srefers to the Rectangle.s.area()runs Rectangle'sarea(): 4 × 5 = 20.0. - In the third pass,
srefers to the Circle with radius 1: ≈ 3.14.
The line s.area() is written once, but it runs three different pieces of code. The loop never asks "what kind of shape is this?", because each object already knows how to work out its own area.
Without polymorphism
Without polymorphism, the loop would need a test for every type:
if (s is a circle) area = Math.PI * r * r;
else if (s is a rectangle) area = w * h;
else if (s is a triangle) ... // and a new branch for every new shapeEvery time a new shape is added, this code (and every other place like it) would have to change. With polymorphism, adding a Triangle only means writing a new class that overrides area(). The loop does not change at all. Code that is open to new types but closed to modification is a key goal of good OOP design (the open/closed principle).
Real-world analogy
Pressing "play" on a phone, a TV remote and a music player: same action, different result in each device.
Polymorphic parameters
Polymorphism is not only for arrays. A method whose parameter has a general type accepts any subclass object:
static void printArea(Shape s) {
System.out.println("Area: " + s.area());
}
printArea(new Circle(2)); // Area: 12.566...
printArea(new Rectangle(3, 4)); // Area: 12.0One method serves every shape, including shapes that will be written next year.
The three things polymorphism needs
- Inheritance (or an interface): the subclasses share a common supertype, here
Shape. - Overriding: each subclass provides its own version of the method, here
area(). - A superclass reference: the code uses the general type (
Shape s), not the specific one.
If any of the three is missing, there is no run-time polymorphism.
Tip: in this example, the area() in Shape returns 0, which has no real meaning: a "shape" in general has no area. In 3.8.1 you will see how to make area() abstract, so that every subclass is forced to provide it.