Unit 12 · lesson
Dynamic Dispatch Chooses the Runtime Behavior
Java can hold a subtype object in a variable typed as a broader type.
Notification n = new UrgentNotification("motor hot");
IO.println(n.format());
The variable's declared type is Notification.
The runtime object is UrgentNotification.
For an overridden instance method, Java selects behavior based on the runtime object. The urgent override runs.
That is dynamic dispatch.
Declared type controls the visible contract
If UrgentNotification adds a method that does not exist in Notification, a variable declared as Notification cannot simply call that subtype-only method without another type check/conversion.
That is useful: code using the broad type should depend on the broad contract.
Polymorphic collection
var notices = new ArrayList<Notification>();
notices.add(new Notification("ready"));
notices.add(new UrgentNotification("battery low"));
notices.add(new UrgentNotification("sensor fault"));
for (Notification notice : notices) {
IO.println(notice.format());
}
One loop, one method call, different runtime behavior.
The caller does not need:
if (notice is urgent) ...
for ordinary formatting because the subtype owns its overridden behavior.
Dispatch trace
For each element, record:
declared collection element type:
runtime object type:
method called in source:
implementation selected at runtime:
observable output:
This separates compile-time view from runtime behavior.
Substitution can expose broken hierarchies
Suppose parent contract says:
format()always returns a nonblank human-readable message.
A subclass override returning null may compile but violate the contract. Every caller relying on the parent promise is now at risk.
Inheritance makes subtype behavior part of the parent's ecosystem. That is why overriding is a responsibility, not merely customization.
Fault experiment
Create a base type with two subclasses. Override one method in both.
Store all three object types in one list of the base type and iterate through them.
Then deliberately break one override so it violates a documented parent rule.
Your test should detect the contract failure without the test needing to special-case the subclass name.
Evidence
Submit the dispatch trace and the shared behavioral test. Explain:
- what the compiler knows from the declared type;
- what the runtime selects from the object type;
- what contract all subtypes still owe the caller.