Java + Spring Boot track

Lesson 09 · Part 2: Object-Oriented Programming

Polymorphism

Polymorphism is the core of OOP, allowing the same message (method call) to perform different actions depending on the type of object. Various child objects are…

Part 2: Object-Oriented Programming4 min readFree lesson

Polymorphism is the core of OOP, allowing the same message (method call) to perform different actions depending on the type of object. Various child objects are stored in parent type variables, and the appropriate implementation is automatically selected at the time of call.

Learning Objectives

  • Understand the meaning of upcasting (child → parent) and downcasting (parent → child)
  • Uses JDK 16+ instanceof pattern matching grammar
  • Define an incomplete parent with the abstract class.
  • Understand the difference between static and dynamic types.

Key concepts

1) Upcasting and downcasting

Animal a = new Dog("Baduki");   //Upcasting (automatic)
Dog d = (Dog) a;                  //Downcasting (explicit)

Upcasting is always safe. For downcasting, the actual type must match, otherwise ClassCastException is displayed.

2) instanceof pattern matching

if (a instanceof Dog d) {
    d.bark();   //variable already cast
}

Pattern matching in JDK 16+ involves type checking + casting + new variable declaration all at once.

3) abstract class

public abstract class Shape {
    public abstract double area();   //no text
    public void describe() {
        System.out.println("area=" + area());
    }
}
  • If there is even one abstract method without a body, the class itself must also be abstract.
  • Cannot be created directly with new — must be implemented by a child
  • Can mix general methods and abstract methods (interfaces and differences)

4) Static type vs dynamic type

Animal a = new Dog("Baduki");
//Static type: Animal, dynamic type: Dog
a.speak();   //Dog.speak() is called (dynamic dispatch)

See as example

Example 1 — Polymorphism.java : Up/downcasting

public class Polymorphism {
    static class Animal {
        void speak() { System.out.println("..."); }
    }
    static class Cat extends Animal {
        @Override void speak() { System.out.println("Meow"); }
        void purr() { System.out.println("purr"); }
    }

    public static void main(String[] args) {
        Animal a = new Cat();   //Upcasting
        a.speak();              //Meow (Dynamic Dispatch)

        //a.purr();             // Compilation error: None in Animal
        Cat c = (Cat) a;        //Downcasting
        c.purr();
    }
}

Execution result

Meow
purr

Note: Since the parent type does not know about the child's additional methods, child-specific calls require downcasting.

Example 2 — InstanceOfPattern.java: Pattern matching

public class InstanceOfPattern {
    sealed interface Animal permits Dog, Cat, Fish {}
    record Dog(String name) implements Animal {}
    record Cat(String name) implements Animal {}
    record Fish(String name) implements Animal {}

    static String introduce(Animal a) {
        if (a instanceof Dog d) return d.name() + "a puppy";
        if (a instanceof Cat c) return c.name() + "is a cat";
        if (a instanceof Fish f) return f.name() + "is a fish";
        return "?";
    }

    public static void main(String[] args) {
        Animal[] zoo = { new Dog("Baduki"), new Cat("butterfly"), new Fish("nemo") };
        for (Animal a : zoo) System.out.println(introduce(a));
    }
}

Execution result

dog playing baduk
butterfly is a cat
nemo is a fish

Note: Combining sealed with record is safe as it allows you to explicitly limit the list of children.

Example 3 — AbstractShape.java: Abstract class

public class AbstractShape {
    static abstract class Shape {
        abstract double area();
        void describe() { System.out.println("area=" + area()); }
    }
    static class Square extends Shape {
        double side;
        Square(double s) { this.side = s; }
        @Override double area() { return side * side; }
    }
    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) {
        //new Shape();        // compile error: abstract
        Shape[] xs = { new Square(2), new Circle(3) };
        for (Shape s : xs) s.describe();
    }
}

Execution result

area=4.0
area=28.274333882308138

Memo: Abstract classes are suitable for combining common behavior + blank space for children to fill.

Example 4 — SwitchPattern.java: switch pattern matching (JDK 21)

public class SwitchPattern {
    sealed interface Pet permits DogPet, CatPet {}
    record DogPet(String name, int age) implements Pet {}
    record CatPet(String name) implements Pet {}

    static String describe(Pet p) {
        return switch (p) {
            case DogPet d -> d.name() + "(" + d.age() + "flesh)";
            case CatPet c -> c.name();
        };
    }

    public static void main(String[] args) {
        System.out.println(describe(new DogPet("Baduki", 3)));
        System.out.println(describe(new CatPet("butterfly")));
    }
}

Execution result

Baduki (3 years old)
butterfly

Note: When the patterns sealed and switch are encountered, the compiler verifies that all cases have been handled.

Common Mistakes

  1. Skip instanceof check before downcasting → ClassCastException
  2. Missing @Override makes it a new method, not an overriding
  3. Write the body in the abstract method
  4. Try to instantiate the abstract class directly as new
  5. Forgetting the constraint that sealed children must have the same module/package

Cleanup

  • Containing child objects in a parent type is the starting point of polymorphism.
  • instanceof pattern matching greatly improves readability
  • abstract and sealed are useful for creating “only allowed children”

Try it yourself

cd 02_Object-Oriented/09_Polymorphism/src
javac Polymorphism.java
java Polymorphism

Example files

The runnable files used in this lesson. Copy them into your own project and experiment.

src/AbstractShape.javaJava
public class AbstractShape {
    static abstract class Shape {
        abstract double area();
        void describe() { System.out.println("area=" + area()); }
    }
    static class Square extends Shape {
        double side;
        Square(double s) { this.side = s; }
        @Override double area() { return side * side; }
    }
    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[] xs = { new Square(2), new Circle(3) };
        for (Shape s : xs) s.describe();
    }
}
src/InstanceOfPattern.javaJava
public class InstanceOfPattern {
    sealed interface Animal permits Dog, Cat, Fish {}
    record Dog(String name) implements Animal {}
    record Cat(String name) implements Animal {}
    record Fish(String name) implements Animal {}

    static String introduce(Animal a) {
        if (a instanceof Dog d) return d.name() + "a puppy";
        if (a instanceof Cat c) return c.name() + "is a cat";
        if (a instanceof Fish f) return f.name() + "is a fish";
        return "?";
    }

    public static void main(String[] args) {
        Animal[] zoo = { new Dog("Baduki"), new Cat("butterfly"), new Fish("nemo") };
        for (Animal a : zoo) System.out.println(introduce(a));
    }
}
src/Polymorphism.javaJava
public class Polymorphism {
    static class Animal {
        void speak() { System.out.println("..."); }
    }
    static class Cat extends Animal {
        @Override void speak() { System.out.println("Meow"); }
        void purr() { System.out.println("purr"); }
    }

    public static void main(String[] args) {
        Animal a = new Cat();
        a.speak();

        Cat c = (Cat) a;
        c.purr();
    }
}
src/SwitchPattern.javaJava
public class SwitchPattern {
    sealed interface Pet permits DogPet, CatPet {}
    record DogPet(String name, int age) implements Pet {}
    record CatPet(String name) implements Pet {}

    static String describe(Pet p) {
        return switch (p) {
            case DogPet d -> d.name() + "(" + d.age() + "flesh)";
            case CatPet c -> c.name();
        };
    }

    public static void main(String[] args) {
        System.out.println(describe(new DogPet("Baduki", 3)));
        System.out.println(describe(new CatPet("butterfly")));
    }
}

Assignment

Problem 1 — Vehicle polymorphism

  • File name: Homework01.java
  • Core concept: abstract parent + child overriding

Requirements

  • Abstract method String describe() in abstract class Vehicle.
  • The children Car and Bike are implemented respectively.
  • Called with Vehicle[] array.

Expected output

Car 4 wheels
Bike 2 wheels

Problem 2 — instanceof pattern matching

  • File name: Homework02.java
  • Core concept: JDK 16+ pattern matching

Requirements

  • Input an integer, string, or list into a Object type variable and output the following:
  • Integer side Number: <value>
  • String side String: <length> characters -Others unknown

Expected output

Number: 42
String: 5 characters
unknown

Check the correct answer

After solving it yourself, compare it with the reference solution below.

Show the reference solution

Try the assignment yourself first — comparing your attempt with a working solution is where most of the learning happens.

Homework01.javaJava
/** Vehicle polymorphism.*/
public class Homework01 {
    public static void main(String[] args) {
        Vehicle[] vs = { new Car(), new Bike() };
        for (Vehicle v : vs) {
            System.out.println(v.describe());
        }
    }
}

abstract class Vehicle {
    abstract String describe();
}

class Car extends Vehicle {
    @Override String describe() { return "Car 4 wheels"; }
}

class Bike extends Vehicle {
    @Override String describe() { return "Bike 2 wheels"; }
}
Homework02.javaJava
import java.util.List;

/** instanceof pattern matching.*/
public class Homework02 {
    public static void main(String[] args) {
        Object[] objs = { 42, "Hello", List.of(1, 2, 3) };
        for (Object o : objs) {
            describe(o);
        }
    }

    static void describe(Object o) {
        if (o instanceof Integer n) {
            System.out.println("Number:" + n);
        } else if (o instanceof String s) {
            System.out.println("string:" + s.length() + "Now");
        } else {
            System.out.println("unknown");
        }
    }
}

Learn this lesson with a mentor

Work through the Java + Spring Boot track on your own, or join a guided cohort at our Pitampura campus and online.

Call NowEnroll Now