Polymorphism in Java β Method Overriding Explained
Polymorphism means "one name, many forms". In Java it appears as two separate mechanisms: method overriding (runtime polymorphism, dispatched by the object's actual type) and method overloading (compile-time, dispatched by the argument types).
Runtime polymorphism β overriding
abstract class Shape {
public abstract double area();
}
class Circle extends Shape {
private final double r;
public Circle(double r) { this.r = r; }
public double area() { return Math.PI * r * r; }
}
class Square extends Shape {
private final double s;
public Square(double s) { this.s = s; }
public double area() { return s * s; }
}
List<Shape> shapes = List.of(new Circle(3), new Square(4));
for (Shape s : shapes) {
System.out.println(s.area()); // calls Circle.area or Square.area
}
The compiler sees Shape s. The JVM, at the call site, looks at the real object and dispatches to its area(). This is dynamic dispatch.
Compile-time polymorphism β overloading
class Logger {
public void log(String msg) { ... }
public void log(String msg, Throwable t) { ... }
public void log(int code, String msg) { ... }
}
// The compiler picks an overload at compile time based on the argument types.
Why it's useful
You can write code against a contract (an interface or abstract class) and the actual implementation varies at runtime:
public interface PaymentProvider {
void charge(BigDecimal amount);
}
public class OrderService {
private final PaymentProvider provider; // Stripe? Paypal? Mock? Doesn't matter.
public void payOrder(Order o) {
provider.charge(o.total());
}
}
Covariant returns
class Shape { public Shape copy() { ... } }
class Circle extends Shape {
@Override
public Circle copy() { return new Circle(...); } // Circle is more specific β legal
}
The static trap
class A { public static String who() { return "A"; } }
class B extends A { public static String who() { return "B"; } }
A a = new B();
a.who(); // "A" β static methods are NOT overridden, only hidden
// The call resolves by compile-time type (A), not runtime type.
Common mistakes
- Expecting
staticto be polymorphic β it isn't. Use instance methods for polymorphic behaviour. - Calling overridable methods from a constructor β subclass override runs on a half-constructed object.
- Overloading + autoboxing β
log(1)might matchlog(int)orlog(Integer). Be explicit if both exist.
Related
Pillar: OOP in Java. Siblings: inheritance, method overloading, interfaces.