Java Polymorphism
"Many forms" — one method call, different behaviors at runtime.
The idea
Polymorphism lets you treat objects of different classes through a common parent type, while each object still uses its own implementation. Calling shape.area() on a list of shapes calls the right method for circles, squares, or triangles — without if-else chains.
Runtime polymorphism (method overriding)
This is the classic OOP polymorphism: subclass overrides a method, and the JVM picks which version to run based on the actual object type.
class Shape { public double area() { return 0; } } class Circle extends Shape { double radius; public Circle(double r) { this.radius = r; } @Override public double area() { return Math.PI * radius * radius; } } class Square extends Shape { double side; public Square(double s) { this.side = s; } @Override public double area() { return side * side; } }
Now treat any shape uniformly:
Shape[] shapes = {
new Circle(5),
new Square(4),
new Circle(3)
};
for (Shape s : shapes) {
System.out.println(s.area());
// Each call uses the actual subclass's area() method
}
// 78.539...
// 16.0
// 28.274...
This is sometimes called dynamic dispatch — the JVM resolves which method to call at runtime.
Compile-time polymorphism (method overloading)
Same method name, different parameter lists. The compiler picks which version based on the arguments.
class Printer { public void print(String s) { System.out.println(s); } public void print(int n) { System.out.println("Number: " + n); } public void print(int[] arr) { for (int n : arr) System.out.println(n); } }
Upcasting and downcasting
You can assign a subclass object to a superclass variable (upcasting) — that's automatic and safe.
Shape s = new Circle(5); // Circle treated as Shape System.out.println(s.area()); // still calls Circle.area()
Downcasting goes the other way and needs an explicit cast plus a runtime check:
Shape s = new Circle(5); if (s instanceof Circle c) { // Java 16+ pattern System.out.println(c.radius); // safe to access Circle-specific field }
if (shape == circle) { ... } else if (shape == square) { ... }. Polymorphism lets you write code that works with whole families of types — the kind of code that scales as your program grows.