Unit 3 · OOP Concepts

3.6.2Run-time polymorphism in action

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.

Java
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
        }
    }
}
Shape[] shapesCircle(3)028.27Rectangle(4, 5)120.0Circle(1)23.14s.area()s.area()Circlearea(): π r²Rectanglearea(): w × hprintedthe loop is written once against Shape; new subclasses need no change to it
Run-time polymorphism: the same call s.area() runs the version that belongs to each actual object

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.

  1. In the first pass, s refers to the Circle with radius 3. s.area() runs Circle's area(): π × 3 × 3 ≈ 28.27.
  2. In the second pass, s refers to the Rectangle. s.area() runs Rectangle's area(): 4 × 5 = 20.0.
  3. In the third pass, s refers 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:

Pseudocode
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 shape

Every 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:

Java
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.0

One method serves every shape, including shapes that will be written next year.

The three things polymorphism needs

  1. Inheritance (or an interface): the subclasses share a common supertype, here Shape.
  2. Overriding: each subclass provides its own version of the method, here area().
  3. 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.