CodeMIRAI
0 XP

Polymorphism

2 min read 2 runnable examples Quiz included

Polymorphism means "many forms": one variable of a parent type can refer to objects of different subclasses, and each object responds in its own way.

Java
class Shape {
    double area() { return 0; }
    String name() { return "shape"; }
}

class Circle extends Shape {
    double r;
    Circle(double r) { this.r = r; }
    @Override double area() { return Math.PI * r * r; }
    @Override String name() { return "circle"; }
}

class Rectangle extends Shape {
    double w, h;
    Rectangle(double w, double h) { this.w = w; this.h = h; }
    @Override double area() { return w * h; }
    @Override String name() { return "rectangle"; }
}

class Square extends Rectangle {
    Square(double side) { super(side, side); }
    @Override String name() { return "square"; }
}

public class Main {
    public static void main(String[] args) {
        Shape[] shapes = { new Circle(1), new Rectangle(3, 4), new Square(5) };
        double total = 0;
        for (Shape s : shapes) {
            System.out.printf("%-9s area %.2f%n", s.name(), s.area());
            total += s.area();
        }
        System.out.printf("total     area %.2f%n", total);
    }
}

The loop only knows it holds Shapes. Which area() runs is decided while the program runs, by the actual object in each slot. This is called dynamic dispatch. Square doesn't even write its own area(): it inherits Rectangle's.

Why it matters

Without polymorphism, code that handles shapes fills up with checks:

Java
if (s is a circle) { ... } else if (s is a rectangle) { ... } else if ...

Every new kind of shape means finding and editing every such chain. With polymorphism, you add a Triangle class with its own area(), and every loop that handles Shapes works with it, unchanged.

Declared type and actual type

Java
Shape s = new Circle(2);
s.area();    // fine: Shape declares area(), and Circle's version runs
s.r;         // error: the declared type, Shape, has no field r

The declared type decides what you may call; the actual object decides which version runs.

instanceof and casting

Java
class Animal {
    String sound() { return "..."; }
}

class Dog extends Animal {
    @Override String sound() { return "Woof"; }
    void fetch() { System.out.println("Fetching the ball!"); }
}

class Cat extends Animal {
    @Override String sound() { return "Meow"; }
}

public class Main {
    public static void main(String[] args) {
        Animal[] pets = { new Dog(), new Cat() };
        for (Animal a : pets) {
            System.out.println(a.sound());
            if (a instanceof Dog) {
                Dog d = (Dog) a;      // safe: checked first
                d.fetch();
            }
        }
        Animal a = new Cat();
        Dog wrong = (Dog) a;          // ClassCastException: a Cat isn't a Dog
    }
}
  • instanceof checks the actual type of an object.
  • A cast like (Dog) a lets you use Dog-only methods, and throws a ClassCastException if the object isn't really a Dog, as the last line shows.
  • Lots of instanceof checks are a hint that the behaviour belongs in an overridden method instead.

Since Java 16, if (a instanceof Dog d) checks and casts in one step. The code runner uses Java 15, so this lesson writes the cast out in full.

Overriding vs overloading

OverridingOverloading
Whata subclass replaces a parent's methodseveral methods in one class share a name
Parametersthe samedifferent
Chosenwhile running, by the actual objectwhen compiling, by the argument types
Finished this lesson?Mark it complete to earn +20 XP and move your progress on.