Polymorphism & dynamic method binding
Late binding, overriding, and how calling through a supertype dispatches to the actual subclass method.
The same call, different behavior
Polymorphism means one reference type can produce many behaviors at runtime. When you call an overridden method through a superclass reference, Java looks at the actual object's class — not the reference's declared type — to decide which method body runs. This decision is made at *run time*, which is why it is called late binding (or dynamic dispatch). Instance methods in Java are late-bound by default.
Worked example: Shape.draw() overridden
class Shape {
void draw() { System.out.println("Shape"); }
}
class Circle extends Shape {
@Override void draw() { System.out.println("Circle"); }
}
class Square extends Shape {
@Override void draw() { System.out.println("Square"); }
}
Now drive them through an array of the supertype:
Shape[] shapes = { new Circle(), new Square(), new Shape() };
for (Shape sh : shapes) {
sh.draw();
}
This prints:
Circle
Square
Shape
Even though every element is *declared* Shape, each sh.draw() dispatches to the overriding method of the real object. The loop never mentions Circle or Square — yet it does the right thing for each. That is the payoff: you can add a Triangle extends Shape later, drop it into the array, and this loop calls Triangle.draw() with no change to this code. Code written against the supertype stays open to new subtypes — the extensibility that polymorphism buys.
@Override is your safety net
The @Override annotation tells the compiler "I intend to override a superclass method." If the signature doesn't actually match one (a typo, a wrong parameter type), the compiler errors instead of silently creating a brand-new, never-called method. Always write it.