Memra

Polymorphism & dynamic dispatch

Reference type vs object type — which method runs, which field you get.

Two types in play

Every object reference has two types: the declared reference type (what the compiler sees) and the runtime object type (what was actually constructed). Polymorphism arises from the gap between them.

class Shape {
    String color = "gray";
    String describe() { return "a shape"; }
}

class Circle extends Shape {
    String color = "red";           // field hiding — not overriding
    @Override
    String describe() { return "a circle"; }  // instance override
}

Shape s = new Circle();            // reference = Shape, object = Circle
System.out.println(s.describe());  // a circle   — runtime object type
System.out.println(s.color);       // gray        — compile-time reference type

The golden rules:

| What | Resolution | Based on | |---|---|---| | Instance method call | Runtime dispatch | Object type | | Static method call | Compile-time | Reference type | | Field access | Compile-time | Reference type |

Polymorphism applies only to instance methods. Fields are never polymorphic — even if a subclass declares a field with the same name, accessing via a parent reference always gives the parent's field.

This design enables the Liskov Substitution Principle: any code accepting a Shape reference works correctly when passed a Circle, because the JVM always dispatches to the actual object's method.

NORMAL ~/memra/learn/java-from-zero/polymorphism-dispatch utf-8 LF