← Back to Java series
🧱
OOP
OOP Β· Prerequisite: previous lecture

09. Polymorphism

Polymorphism means a **parent reference can point to a child object**, and the method actually called is decided at runtime. Together with `abstract` classes, it's the backbone of flexible OOP design.

JavaOOPpolymorphismabstract class
Duration
⏱ ~1.5-2 hours
Level
πŸ“Š Intermediate
Prerequisite
🎯 Previous lecture or equivalent knowledge
OUTCOME
Polymorphism means a **parent reference can point to a child object**, and the method actually called is decided at runtime. Together with `abstract` classes, it's the backbone of flexible OOP design.

What you'll learn

  • 1Understand polymorphism via parent reference / child object
  • 2Declare an `abstract` class and abstract methods
  • 3Use `instanceof` and JDK 16+ pattern matching
  • 4Choose between an abstract class and an interface (interface in lecture 10)

Overview

Polymorphism means a **parent reference can point to a child object**, and the method actually called is decided at runtime. Together with `abstract` classes, it's the backbone of flexible OOP design.

Core Concepts

1) Parent reference / child object

java
Animal a = new Dog("Rex");   // upcasting
a.speak();                  // calls Dog.speak() (dynamic dispatch)

Even though the reference type is `Animal`, the actual object is `Dog` so the override wins.

2) `abstract` class

java
abstract class Shape {
    abstract double area();      // no body β€” must be implemented by subclass
    void print() { System.out.println("area=" + area()); }
}
  • Has at least one abstract method
  • Cannot be instantiated with `new`
  • Subclasses **must** implement every abstract method

3) `instanceof` and pattern matching

java
Object o = "hello";
if (o instanceof String s) {
    System.out.println(s.toUpperCase());   // JDK 16+ pattern matching
}

4) Downcasting

java
Animal a = new Dog("Rex");
Dog d = (Dog) a;            // safe because the actual object is Dog
// Cat c = (Cat) a;         // ClassCastException at runtime

Always check with `instanceof` before downcasting.

Examples

Example 1 β€” `ShapeDemo.java`: abstract class polymorphism

java
public class ShapeDemo {
    static abstract class Shape {
        abstract double area();
    }
    static class Rectangle extends Shape {
        double w, h;
        Rectangle(double w, double h) { this.w = w; this.h = h; }
        @Override double area() { return w * h; }
    }
    static class Circle extends Shape {
        double r;
        Circle(double r) { this.r = r; }
        @Override double area() { return Math.PI * r * r; }
    }

    public static void main(String[] args) {
        Shape[] shapes = { new Rectangle(3, 4), new Circle(2) };
        for (Shape s : shapes) System.out.printf("%.3f%n", s.area());
    }
}

**Output**

text
12.000
12.566

**Note:** the same `s.area()` call dispatches to different implementations at runtime.

Example 2 β€” `Speak.java`: dynamic dispatch

java
public class Speak {
    static class Animal { void speak() { System.out.println("..."); } }
    static class Dog extends Animal { @Override void speak() { System.out.println("Woof"); } }
    static class Cat extends Animal { @Override void speak() { System.out.println("Meow"); } }

    public static void main(String[] args) {
        Animal[] zoo = { new Dog(), new Cat(), new Animal() };
        for (Animal a : zoo) a.speak();
    }
}

**Output**

text
Woof
Meow
...

**Note:** the array element type is `Animal`, but each call resolves to the actual subtype's method.

Example 3 β€” `PatternMatch.java`: `instanceof` pattern matching

java
public class PatternMatch {
    static String describe(Object o) {
        if (o instanceof Integer i) return "int " + i;
        if (o instanceof String s)  return "string [" + s + "]";
        return "unknown";
    }

    public static void main(String[] args) {
        System.out.println(describe(42));
        System.out.println(describe("hello"));
        System.out.println(describe(3.14));
    }
}

**Output**

text
int 42
string [hello]
unknown

**Note:** the binding variable `i` / `s` is only in scope after the test passes.

Example 4 β€” `AbstractError.java`: cannot `new` an abstract class

java
public class AbstractError {
    static abstract class Vehicle { abstract void move(); }
    // new Vehicle();   // compile error

    static class Car extends Vehicle {
        @Override void move() { System.out.println("car rolls"); }
    }

    public static void main(String[] args) {
        Vehicle v = new Car();
        v.move();
    }
}

**Output**

text
car rolls

Common Mistakes

  1. Forgetting that you can't instantiate an `abstract` class
  2. Downcasting without checking β€” `ClassCastException` at runtime
  3. Confusing overloading with overriding (overloading is compile-time, overriding is runtime)
  4. Leaving an abstract method unimplemented in a concrete subclass β†’ compile error
  5. Reaching for `instanceof` to switch on type β€” prefer polymorphism

Summary

  • Parent reference + child object β†’ dynamic dispatch
  • `abstract` defines a contract; subclasses must fulfill it
  • Pattern matching `instanceof` is the modern, type-safe form

Practice

# Practice - 09. Polymorphism

## Exercise 1 β€” `Pay` hierarchy

  • File: `Homework01.java`
  • Key concepts: abstract class, override

Requirements

  • Abstract class `Pay` with abstract method `long pay()`.
  • `Hourly(int hours, int rate)` and `Salary(long monthly)` extend it.
  • In main, store both in `Pay[]` and print each `pay()`.

Expected output

text
hourly: 200000
salary: 3000000

## Exercise 2 β€” Pattern-matching describe

  • File: `Homework02.java`
  • Key concepts: `instanceof` pattern matching

Requirements

  • Write `static String describe(Object o)` for Integer / Double / String / else.

Expected output

text
int 7
double 3.140
string [hi]
unknown

## Solutions After trying it yourself, compare with [`answer/`](./answer/).

Solution code (homework/answer/)

answer/Homework01.java

java
/** Pay hierarchy. */
public class Homework01 {
    public static void main(String[] args) {
        Pay[] payroll = { new Hourly(10, 20_000), new Salary(3_000_000) };
        String[] names = { "hourly", "salary" };
        for (int i = 0; i < payroll.length; i++) {
            System.out.println(names[i] + ": " + payroll[i].pay());
        }
    }
}

abstract class Pay { abstract long pay(); }
class Hourly extends Pay {
    int hours, rate;
    Hourly(int h, int r) { this.hours = h; this.rate = r; }
    @Override long pay() { return (long) hours * rate; }
}
class Salary extends Pay {
    long monthly;
    Salary(long m) { this.monthly = m; }
    @Override long pay() { return monthly; }
}

answer/Homework02.java

java
/** Pattern-matching describe. */
public class Homework02 {
    public static void main(String[] args) {
        System.out.println(describe(7));
        System.out.println(describe(3.14));
        System.out.println(describe("hi"));
        System.out.println(describe(true));
    }

    static String describe(Object o) {
        if (o instanceof Integer i) return "int " + i;
        if (o instanceof Double d)  return String.format("double %.3f", d);
        if (o instanceof String s)  return "string [" + s + "]";
        return "unknown";
    }
}

Try It Yourself

bash
cd 02_oop/09_polymorphism/src
javac ShapeDemo.java
java ShapeDemo

Next Lecture

[10_Interface](../10_μΈν„°νŽ˜μ΄μŠ€/) β€” `interface`, `implements`, `default` methods.

Example code / lecture materials

All lecture materials and example code are openly available on GitHub.

View on GitHub β†—