Runtime Polymorphism and Dynamic Method Dispatch
In Shape s = ...; s.area(); the compiler doesn't know which shape s will hold: a circle, a rectangle or a square might come from user input. So for overridden methods, the final choice is left to the JVM, at runtime: it looks at the actual object, finds the right area() for its class, and runs that. This mechanism is called dynamic method dispatch, and it's what makes runtime polymorphism work.
One Loop, Mixed Shapes
public class Shapes {
static class Shape {
double area() { return 0; }
String name() { return "Shape"; }
}
static class Circle extends Shape {
double r;
Circle(double r) { this.r = r; }
@Override double area() { return Math.PI * r * r; }
@Override String name() { return "Circle"; }
}
static class Rectangle extends Shape {
double w, h;
Rectangle(double w, double h) { this.w = w; this.h = h; }
@Override double area() { return w * h; }
@Override String name() { return "Rectangle"; }
}
static class Square extends Rectangle {
Square(double side) { super(side, side); }
@Override String name() { return "Square"; }
}
public static void main(String[] args) {
Shape[] shapes = { new Circle(1), new Rectangle(2, 3), new Square(4) };
double total = 0;
for (Shape s : shapes) {
System.out.printf("%-9s %6.2f%n", s.name(), s.area());
total += s.area();
}
System.out.printf("%-9s %6.2f%n", "Total", total);
}
}Circle 3.14
Rectangle 6.00
Square 16.00
Total 25.14How it works
sis declared asShape, but holds aCircle, then aRectangle, then aSquare.- For each
s.area(), the JVM checks the object's class and runs that class'sarea(). Squaredoesn't declarearea(). The JVM walks up to its parent,Rectangle, and runsRectangle.area(): 4 × 4 = 16.Squaredoes declarename(), so its own version runs.
Try it yourself: add a Triangle with a base and height. The loop in main needs no change.
The Lookup, Step by Step
The rule the JVM follows:
- Start at the actual class of the object (not the variable's type).
- If that class declares the method, run it.
- Otherwise move to its parent and check again, up to
Object.
The compiler has already guaranteed the search will succeed: s.area() compiled only because Shape (the declared type) has an area() method.
What Is and Isn't Dispatched Dynamically
| Member | Chosen by | Dynamic? |
|---|---|---|
| Overridable instance methods | The object's class, at runtime | Yes |
static methods | The declared type, at compile time | No |
private methods | The declaring class | No |
final methods | Only one version exists | No (nothing to choose) |
| Fields | The declared type | No |
| Overloads | The compiler picks the signature | No (the signature is fixed; the implementation of it can still be dispatched) |
Common Mistakes
Thinking the variable's type picks the method. It only decides what you may call.
Calling an overridable method from a constructor. The child's override runs before the child's fields are set ("Order of Initialization" showed the order), so it can see zeros and nulls.
Expecting fields to be dispatched. s.r doesn't compile through a Shape variable, and redeclared fields follow the declared type.
Under the Hood
The compiler writes an invokevirtual instruction that names the declared type's method:
85: invokevirtual #36 // Method Shapes$Shape.name:()Ljava/lang/String;
93: invokevirtual #40 // Method Shapes$Shape.area:()DThe bytecode says Shape.area, yet Circle.area runs. At runtime, invokevirtual uses the object's class to find the actual method. To keep that fast, HotSpot gives each class a method table (a "vtable"): an array of the class's overridable methods, where an overriding method takes the same slot as the method it overrides. area() sits in the same slot for Shape, Circle, Rectangle and Square; for Square, that slot simply points at Rectangle.area. A call becomes "read the object's class, read slot N, jump", with no searching.
The JIT compiler goes further. If a call site has only ever seen one or two classes, it inlines those methods directly with a quick type check. So virtual calls in Java are rarely slower than direct calls in practice.
Interview Corner
What is dynamic method dispatch?
It's the mechanism by which a call to an overridden method is resolved at runtime, using the class of the actual object rather than the type of the reference. The JVM starts at the object's class and uses the most specific overriding method. It's how Java implements runtime polymorphism: Shape s = new Circle(); s.area(); runs Circle.area().
How does the JVM implement dynamic dispatch efficiently?
invokevirtual names the method on the declared type. HotSpot keeps a virtual method table per class, where overriding methods occupy the same slot as the methods they override, so a call reads the slot from the object's class instead of searching. The JIT also inlines call sites that only see one or two receiver classes, guarded by a type check.
Practice
- Easy: Give
Shapea methoddescribe()that returnsname() + " with area " + area(), without overriding it in any subclass. What does it print for a square, and why? - Harder: Add a
Cubeclass that extendsSquareand overridesarea()to return the surface area (6 × side²). Predict all four lines before running.
Key Takeaways
Was this tutorial helpful?