Codectionary / Developer documentation / Java

Polymorphism

Polymorphism - literally 'many forms' - lets objects of different subclasses be treated through a common parent type or interface, while each object still behaves according to its own specific class at runtime. This is achieved through method overriding: a variable declared with a parent type can hold any subclass object, and calling an overridden method automatically runs the correct subclass version, a mechanism called dynamic (runtime) dispatch.

Syntax

ParentType variable = new ChildType();

Examples

Runtime Polymorphism with Method Overriding

The same method call produces different behavior depending on the actual object type.

class Animal {
    public void makeSound() {
        System.out.println("Some generic sound");
    }
}

class Dog extends Animal {
    @Override
    public void makeSound() {
        System.out.println("Woof!");
    }
}

class Cat extends Animal {
    @Override
    public void makeSound() {
        System.out.println("Meow!");
    }
}

Animal[] animals = { new Dog(), new Cat(), new Animal() };
for (Animal a : animals) {
    a.makeSound();  // calls the correct version for each actual object type
}
// Woof!
// Meow!
// Some generic sound

Polymorphism with Interfaces

Treating different implementing classes uniformly through a shared interface type.

interface Shape {
    double area();
}

class Circle implements Shape {
    private double radius;
    public Circle(double radius) { this.radius = radius; }
    public double area() { return Math.PI * radius * radius; }
}

class Square implements Shape {
    private double side;
    public Square(double side) { this.side = side; }
    public double area() { return side * side; }
}

Shape[] shapes = { new Circle(3), new Square(4) };
for (Shape s : shapes) {
    System.out.printf("Area: %.2f%n", s.area());
}

Polymorphism in Method Parameters

A method that accepts the parent type can work with any subclass, without needing to know the specific type.

class Employee {
    protected String name;
    public Employee(String name) { this.name = name; }
    public double calculatePay() { return 0; }
}

class FullTime extends Employee {
    public FullTime(String name) { super(name); }
    @Override
    public double calculatePay() { return 5000; }
}

class Contractor extends Employee {
    public Contractor(String name) { super(name); }
    @Override
    public double calculatePay() { return 3000; }
}

public class Payroll {
    public static void printPay(Employee e) {  // works with ANY Employee subclass
        System.out.println(e.name + ": $" + e.calculatePay());
    }

    public static void main(String[] args) {
        printPay(new FullTime("John"));
        printPay(new Contractor("Zain"));
    }
}

Best practices

  • Declare variables, parameters, and return types using the parent type or interface when you want to write flexible, extensible code
  • Rely on polymorphism instead of type-checking with instanceof and manual casting where possible - let overridden methods handle the differences
  • Remember polymorphism applies to overridden instance methods, not fields or static methods, which are resolved based on the declared type instead
  • Design parent classes/interfaces around behavior that genuinely varies by subclass, so polymorphism adds real value rather than just ceremony

Runtime & versions

Java Virtual Machine. Feature availability depends on the language and runtime version used by your project; consult the official reference below.

Java Language Specification

Specifications & further reading