Shiklo Programming
EN
  • English
  • Español
  • Português
  • Deutsch
  • Français
  • 日本語
  • हिन्दी

Java Polymorphism

Polymorphism means "many forms". In Java it means that a variable of a superclass type can hold an object of any of its subclasses, and when you call an overridden method, the version of the actual object runs. This lets one piece of code work with many different kinds of objects. It builds directly on Inheritance.

A superclass variable, many objects

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 Square extends Shape {
    double side;
    Square(double side) { this.side = side; }

    @Override
    double area() {
        return side * side;
    }
}

public class Main {
    public static void main(String[] args) {
        Shape[] shapes = {new Circle(1), new Square(3), new Circle(2.5)};
        double total = 0;
        for (Shape s : shapes) {
            System.out.printf("%s: %.2f%n", s.getClass().getSimpleName(), s.area());
            total += s.area();
        }
        System.out.printf("Total area: %.2f%n", total);
    }
}
Output
Circle: 3.14
Square: 9.00
Circle: 19.63
Total area: 31.78

The array has the type Shape[], yet it holds circles and squares. In the loop, s.area() runs the circle's or the square's method, depending on what s really is. Java decides this while the program runs; this is called dynamic dispatch.

The loop does not need to know which shapes exist. Add a Triangle subclass later and the loop works for it without any change. That is the main benefit of polymorphism.

Methods that accept any subclass

A method parameter of the superclass type accepts every subclass:

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

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

class Cow extends Animal {
    @Override
    String sound() {
        return "moo";
    }
}

public class Main {
    static void speak(Animal a) {
        System.out.println("The animal says " + a.sound());
    }

    public static void main(String[] args) {
        speak(new Cat());
        speak(new Cow());
        speak(new Animal());
    }
}
Output
The animal says meow
The animal says moo
The animal says ...

instanceof and casting

A Shape variable only lets you call what Shape has. To use something that only a subclass has, first check the real type with instanceof. The modern form checks and creates a variable of the subclass type in one step:

class Shape {
}

class Circle extends Shape {
    double r = 2;
}

class Square extends Shape {
    double side = 3;
}

public class Main {
    public static void main(String[] args) {
        Shape[] shapes = {new Circle(), new Square()};
        for (Shape s : shapes) {
            if (s instanceof Circle c) {
                System.out.println("A circle with radius " + c.r);
            } else if (s instanceof Square sq) {
                System.out.println("A square with side " + sq.side);
            }
        }
    }
}
Output
A circle with radius 2.0
A square with side 3.0

If you find yourself writing many instanceof checks, it is often a sign that the behaviour should be an overridden method instead. The Shape class above returns 0 for area(), which is not really meaningful; the next topic, Abstraction, shows how to say "every shape must have an area" properly.

Practice exercises

  1. Add a Triangle subclass (base and height) to the first example and put one in the array.
  2. Write a class Employee with a method pay(), and subclasses Manager and Intern that pay differently; print the total pay of a mixed array.
  3. In the speak example, add a Dog subclass without changing the speak method.