4 minutes
3: Polymorphism in Java
Polymorphism and Abstract Types in Java – Dynamic Dispatch in Action
Introduction
Polymorphism is what lets a program treat different objects the same way, without knowing their exact types at compile time. In Java, this power comes from dynamic dispatch, abstract classes, and interfaces. In this post, we’ll explore how polymorphism actually works in Java, clarify the roles of abstract classes versus interfaces, and discuss when to choose one over the other.
1. What Is Polymorphism?
The word polymorphism comes from Greek, meaning “many shapes.” In Java, it means you can write code that works on the superclass or interface type, and at runtime it executes the subclass’s or implementation’s method.
public interface Shape {
double area();
}
public class Circle implements Shape {
private double radius;
public Circle(double r) { radius = r; }
@Override
public double area() { return Math.PI * radius * radius; }
}
public class Rectangle implements Shape {
private double width, height;
public Rectangle(double w, double h) { width = w; height = h; }
@Override
public double area() { return width * height; }
}
A method can accept any Shape
:
public static void printArea(Shape s) {
System.out.println("Area = " + s.area());
}
Even though printArea
knows only about Shape
, it calls the right area()
implementation at runtime.
Teaching Tip:
Demonstrate polymorphism by looping over a List<Shape>
containing both Circle
and Rectangle
objects.
2. Dynamic Dispatch Under the Hood
At compile time, the compiler checks that a call like s.area()
is valid on type Shape
. But at runtime, the JVM uses the object’s actual class to find the method implementation, this is dynamic dispatch (a.k.a. virtual method dispatch).
- The method table (v-table) stored per class lets the JVM look up and invoke the right method quickly.
final
methods andstatic
methods bypass dynamic dispatch: the compiler binds those calls early.
Quirk:
Calling a static
method on a subclass reference doesn’t use the subclass’s override, it uses the type of the reference.
3. Abstract Classes vs. Interfaces
Both let you define types that can’t be instantiated directly, but they serve different purposes:
Feature | Abstract Class | Interface |
---|---|---|
Multiple inheritance | No | Yes (since Java 8 with default methods) |
State (fields) | Can have fields | Cannot have instance fields (only constants) |
Constructors | Yes | No |
Default method behavior | Override or inherit as usual | Default methods (since Java 8) |
When to use | “Is-a” with shared code/state | Pure contract with no shared state |
Example Abstract Class:
public abstract class Employee {
protected String name;
public Employee(String name) { this.name = name; }
public abstract double calculatePay();
}
Example Interface:
public interface Taxable {
double taxAmount();
default void printTax() { System.out.println("Tax: " + taxAmount()); }
}
4. When to Choose Interfaces vs. Abstract Classes
Use abstract classes when:
- You want to share code or state among related classes.
- You need non-public members or constructor logic.
Use interfaces when:
- You need a pure specification with no state.
- You expect many unrelated classes to implement the same API.
- You want multiple inheritance of behavior via default methods.
Teaching Tip:
Create a class hierarchy for employees to show shared code in an abstract class, then add an unrelated Taxable
interface for separate responsibilities.
5. Casting and instanceof
Sometimes you know more specific information at runtime:
Shape s = getShape();
if (s instanceof Circle) {
Circle c = (Circle) s;
System.out.println("Radius = " + c.getRadius());
}
Use instanceof
sparingly, it can be a sign you’re breaking polymorphism’s benefits.
6. Real-World Patterns
- Strategy Pattern: Define a family of algorithms in interfaces and swap implementations at runtime.
- Template Method Pattern: Put skeleton logic in an abstract class and let subclasses fill in specifics.
Discussing these patterns illustrates why polymorphism matters beyond basic OOP exercises.
Key Takeaways
- Polymorphism lets you write code against abstract types, while dynamic dispatch picks the right implementation.
- Abstract classes and interfaces both define types, but differ in inheritance, state, and shared code.
- Choose based on whether you need shared implementation (
abstract class
) or a pure contract (interface
). - Patterns like Strategy and Template Method build on polymorphism to solve real problems.
Next up: Java’s memory model and garbage collection, understand how objects live, die, and how the JVM keeps your program running smoothly.