Polymorphism
Polymorphism lets one call site dispatch to the right implementation at runtime. Java offers it via inheritance (extends + virtual methods), interfaces (multiple-inheritance of behaviour), and pattern matching on sealed hierarchies (Java 21+).
virtual dispatch, interfaces, sealed
EXAMPLE
// 1) Classic virtual dispatch
public class Animal {
public String sound() { return "..."; }
@Override public String toString() { return getClass().getSimpleName() + "(" + sound() + ")"; }
}
public class Dog extends Animal {
@Override public String sound() { return "woof"; }
}
public class Cat extends Animal {
@Override public String sound() { return "meow"; }
}
Animal a = new Dog();
System.out.println(a.sound()); // 'woof' — runtime type wins
// 2) Generic polymorphism + List<Animal>
List<Animal> zoo = List.of(new Dog(), new Cat(), new Dog());
zoo.forEach(animal -> System.out.println(animal.sound()));
// Output: woof, meow, woof
// 3) Interface polymorphism — usually preferred over deep class hierarchies
public interface Payment {
Receipt charge(long amountCents);
}
public class CreditCard implements Payment {
@Override public Receipt charge(long amountCents) { /* ... */ return new Receipt("cc", amountCents); }
}
public class PayPal implements Payment {
@Override public Receipt charge(long amountCents) { /* ... */ return new Receipt("pp", amountCents); }
}
public class Checkout {
public Receipt pay(Payment method, long amountCents) {
return method.charge(amountCents);
}
}
// Checkout doesn't know or care which payment type.
// Adding a fourth method = one new class + one DI registration.
// 4) sealed interfaces — closed hierarchy (Java 17+)
public sealed interface Shape permits Circle, Square, Rect {}
public record Circle(double r) implements Shape {}
public record Square(double side) implements Shape {}
public record Rect (double w, double h) implements Shape {}
// Pattern matching switch (Java 21+) — exhaustive
static double area(Shape s) {
return switch (s) {
case Circle c -> Math.PI * c.r() * c.r();
case Square sq -> sq.side() * sq.side();
case Rect r -> r.w() * r.h();
};
}
// Compiler enforces all variants handled; adding a new permits without updating switches is a compile error.
// 5) Records + sealed hierarchies — the modern data shape
public sealed interface Result<T> permits Success, Failure {}
public record Success<T>(T value) implements Result<T> {}
public record Failure<T>(Throwable err) implements Result<T> {}
static <T> T unwrap(Result<T> r) {
return switch (r) {
case Success<T> s -> s.value();
case Failure<T> f -> throw new RuntimeException(f.err());
};
}
// 6) Covariant return types (Java 5+)
public class Animal2 { public Animal2 clone() { return new Animal2(); } }
public class Dog2 extends Animal2 { @Override public Dog2 clone() { return new Dog2(); } }
// Caller of Dog2.clone() gets Dog2, not Animal2 — strongly typed override.
// 7) Default + static methods on interfaces
public interface Logger {
void log(LogLevel level, String msg);
default void info(String msg) { log(LogLevel.INFO, msg); }
default void error(String msg) { log(LogLevel.ERROR, msg); }
static Logger silent() { return (l, m) -> {}; }
}
// Implementers only need to provide log(); info/error come for free unless overridden.
// 8) Diamond resolution — multiple default methods
public interface A { default String name() { return "A"; } }
public interface B { default String name() { return "B"; } }
public class C implements A, B {
@Override public String name() { return A.super.name(); } // pick explicitly
}
// 9) Generic + bounded
public class Cache<K, V extends Cacheable> {
private final Map<K, V> map = new HashMap<>();
public V get(K k) { return map.get(k); }
}
public interface Cacheable { String cacheKey(); }
// 10) Composition over inheritance — composition uses polymorphism too
public class TimedCache {
private final Cache<String, ?> inner;
public TimedCache(Cache<String, ?> inner) { this.inner = inner; }
public Object get(String k) {
long start = System.nanoTime();
try { return inner.get(k); }
finally { System.out.println("took " + (System.nanoTime() - start) + "ns"); }
}
}
// 11) Liskov red flags
// • Override throws UnsupportedOperationException — caller can't substitute safely
// • Subclass tightens preconditions — LSP violation
// • Square : Rectangle with separate setWidth/setHeight — classic LSP violation
// • Need 'if (obj instanceof X)' after a polymorphic call — polymorphism failed
// 12) Pattern matching for instanceof (Java 16+)
String describe(Object o) {
if (o instanceof Dog d) return "dog: " + d.sound();
if (o instanceof Cat c) return "cat: " + c.sound();
return "unknown";
}
// 13) Where polymorphism is the WRONG tool
// • Branching on type AND on state — state pattern instead
// • Adding a new subtype needs N file edits — visitor pattern or sealed + switch
// • Polymorphic call in a tight loop — measure; consider sealed for inlining hints
// • Hierarchy depth >= 3 levels — almost always reconsider; flatten with composition
// 14) Common bugs
// • Missing @Override on intended override → typo creates a new method silently
// • Calling a virtual method from a constructor → subclass code runs on a half-built object
// • Adding a permits type without updating switches → compile error; embrace it
// • Mixed inheritance + interface dispatch with same method name → resolve explicitly
// • Inheritance for code reuse only → prefer composition
// • Polymorphism via 'object' + downcast → usually a missing interface
// • Forgetting to make a base class abstract → instantiable with no meaningful behaviour
Why it matters
Java polymorphism in 2025 means interfaces injected via DI, sealed hierarchies with pattern-matched switches, and inheritance reserved for abstract bases with shared concrete behaviour. Records make data variants trivial, and the compiler enforces exhaustiveness on sealed switches — safety nets old-school extends never had.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Example
Example
Animal a = new Dog(); System.out.println(a.speak()); // "woof" — runtime dispatchTry it Yourself »
Discussion
Loading…