Polymorphism

OOP · lesson 6 of 43 · 3 min read

How one call site runs different code depending on the actual object, and why that kills if/else chains.

Open this lesson in the learning hub

Key points

  • One declared type, many runtime types. The variable says Shape; the object decides which area() runs.
  • Java picks the method from the actual object at runtime, not from the declared type. That is dynamic dispatch.
  • This is how you delete type-checking if/else chains. Add a new class and existing code keeps working untouched.
  • Overriding is runtime and polymorphic. Overloading (same name, different parameters) is resolved at compile time.
  • Fields are not polymorphic. A subclass field with the same name hides the parent field, it does not override it.

Example

import java.util.List;

public class Main {
    interface Shape { double area(); }

    record Circle(double r)    implements Shape { public double area() { return Math.PI * r * r; } }
    record Square(double side) implements Shape { public double area() { return side * side; } }

    // One method handles every Shape that will ever exist. No if/else on type.
    static double totalArea(List<Shape> shapes) {
        double sum = 0;
        for (Shape s : shapes) sum += s.area();   // chosen at runtime
        return sum;
    }

    public static void main(String[] args) {
        List<Shape> shapes = List.of(new Circle(1), new Square(2), new Circle(3));

        for (Shape s : shapes) {
            System.out.printf("%-18s area=%.2f%n", s, s.area());
        }
        System.out.printf("total = %.2f%n", totalArea(shapes));
    }
}

Program against the interface and new implementations slot in without touching old code.

This is a reading copy. The full lesson — with the visual explainer, the interactive lab and a Run button for the code — lives in the OOP course, and every lesson in it is listed on the OOP contents page.