Memra

Polymorphism & dynamic dispatch

◈ 4 cards

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:

WhatResolutionBased on
Instance method callRuntime dispatchObject type
Static method callCompile-timeReference type
Field accessCompile-timeReference 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.

the object from new Circle()runtime type CircleCircle partcolor = "red" · describe() winsShape partcolor = "gray" · describe() shadowed
Hiding a field replaces nothing — the object carries both. s.describe() runs the Circle part because dispatch follows the object; s.color reads the Shape part because s is declared Shape.
NORMAL ~/memra/learn/java-from-zero/polymorphism-dispatch utf-8 LF