Polymorphism — Overloading, Dynamic Dispatch, and instanceof
1056 words
5 min read
Visual companion
Python
Type and operator map
Python Week 1: the first filter for runtime behavior
View
Revision summary
What this note is really saying
Short form
# Polymorphism — Overloading, Dynamic Dispatch, and instanceof ## 🎯 Learning Objectives By the end of this topic, you will be able to: - Distinguish compile-time (overloading) from runtime (overriding) polymorphism - Write overloaded methods correctly - Trace dynamic dispatch through class hierarchies - Safely use...

Polymorphism — Overloading, Dynamic Dispatch, and instanceof
🎯 Learning Objectives
By the end of this topic, you will be able to:
- Distinguish compile-time (overloading) from runtime (overriding) polymorphism
- Write overloaded methods correctly
- Trace dynamic dispatch through class hierarchies
- Safely use
instanceofand casting - Explain how the JVM resolves method calls at runtime
📋 Prerequisites
- Inheritance (07-inheritance.md): extends, super, overriding
1. Intuition
Compile-time polymorphism (overloading): Same name, different parameters — like a chef who can cook eggs in different ways (fried, scrambled, boiled). The recipe chosen depends on what ingredients you provide.
Runtime polymorphism (overriding): Same name, same parameters, different behavior — like different chefs each making their signature pasta dish. You ask for "pasta" and get the chef's version. The actual dish depends on which chef you got.
2. Compile-Time Polymorphism (Method Overloading)
2.1 Rules
javapublic class Printer { // Same name, different parameters public void print(int x) { } public void print(String s) { } public void print(int x, String s) { } public void print(String s, int x) { } // Different order }
Rules:
- Methods must have the same name
- Must have different parameter lists (type, count, or order)
- Return type CAN differ but can't be the sole differentiator
- Resolved at compile time
2.2 Why Overloading?
javaSystem.out.println(42); // print(int) System.out.println("Hello"); // print(String) System.out.println(3.14); // print(double)
Overloading provides convenience — one method name for logically similar operations.
2.3 Autoboxing and Widening in Overloading
When the compiler selects which overloaded method to call, it prefers (in order):
- Exact match (no conversion needed)
- Widening (e.g., int → long → double)
- Autoboxing (int → Integer)
- Varargs
javapublic class OverloadDemo { static void go(int x) { System.out.println("int"); } static void go(long x) { System.out.println("long"); } static void go(Integer x) { System.out.println("Integer"); } static void go(int... x) { System.out.println("varargs"); } public static void main(String[] args) { go(5); // "int" — exact match wins } }
3. Runtime Polymorphism (Dynamic Dispatch)
3.1 How Dynamic Dispatch Works
javaclass Animal { void sound() { System.out.println("Animal makes sound"); } } class Dog extends Animal { @Override void sound() { System.out.println("Dog barks"); } } class Cat extends Animal { @Override void sound() { System.out.println("Cat meows"); } } public class Test { public static void main(String[] args) { Animal[] animals = {new Dog(), new Cat(), new Animal()}; for (Animal a : animals) { a.sound(); // JVM decides at runtime which sound() to call } } } // Output: // Dog barks // Cat meows // Animal makes sound
3.2 Virtual Method Table (VTable)
The JVM maintains a vtable for each class — a table of method pointers. When
a.sound() is called:- JVM gets the actual class of
a(Dog, Cat, or Animal) - Looks up
soundin that class's vtable - Calls the found implementation (Diagram)
3.3 Polymorphism Enables Flexibility
javapublic class PaymentProcessor { public void processPayment(PaymentMethod method, double amount) { method.pay(amount); // Works with ANY PaymentMethod subclass } } // Later, add new payment types without changing processPayment! class CreditCard extends PaymentMethod { void pay(double a) { /* ... */ } } class UPI extends PaymentMethod { void pay(double a) { /* ... */ } } class Crypto extends PaymentMethod { void pay(double a) { /* ... */ } }
4. Upcasting and Downcasting
javaDog dog = new Dog(); Animal animal = dog; // Upcast: implicit, safe Dog dog2 = (Dog) animal; // Downcast: explicit, risky
4.1 Upcasting
javapublic void feed(Animal a) { a.eat(); } // Accepts any Animal subclass feed(new Dog()); // Dog upcast to Animal feed(new Cat()); // Cat upcast to Animal
4.2 Downcasting with instanceof
javaAnimal a = new Dog(); if (a instanceof Dog) { Dog d = (Dog) a; // Safe d.bark(); } if (a instanceof Cat) { Cat c = (Cat) a; // Won't execute }
Java 16+ Pattern Matching for instanceof:
javaif (a instanceof Dog d) { d.bark(); // No explicit cast needed! }
5. Polymorphism and Method Binding Summary
| Type | Also called | When resolved | Based on | Mechanism |
|---|---|---|---|---|
| Overloading | Static/compile-time polymorphism | Compile time | Reference type + parameter types | Compiler selects method |
| Overriding | Dynamic/runtime polymorphism | Runtime | Actual object type | VTable lookup |
6. Common Pitfalls
Pitfall 1: Overloading Instead of Overriding (Missing @Override)
Pitfall 2: Fields Are Not Polymorphic (shadowing)
Pitfall 3: Static Methods Use Static Binding (no dynamic dispatch)
Pitfall 4: Downcasting Without instanceof → ClassCastException
7. Practice Questions
Q1: What is the output?javaclass A { String f() { return "A"; } } class B extends A { String f() { return "B"; } } public class Test { public static void main(String[] args) { A obj = new B(); System.out.println(obj.f()); } }Answer:B— dynamic dispatch calls B's f(). Q2: What is the difference between overloading and overriding?Answer: Overloading: same name, different params, compile-time. Overriding: same signature, different class, runtime. Q3: Can you overload by changing return type only?Answer: No.int foo()anddouble foo()are ambiguous — compiler can't tell which to call. Q4: What does instanceof return for null?Answer:false.null instanceof AnyTypealways returns false (no NullPointerException). Q5: What is the output?javaclass Parent { static void greet() { System.out.println("Parent static"); } void hello() { System.out.println("Parent hello"); } } class Child extends Parent { static void greet() { System.out.println("Child static"); } void hello() { System.out.println("Child hello"); } } public class Test { public static void main(String[] args) { Parent p = new Child(); p.greet(); p.hello(); } }Answer: "Parent static" then "Child hello". Static methods use static binding (reference type). Instance methods use dynamic binding (object type).
📐 Key Concepts
| Concept | Resolution | Use |
|---|---|---|
| Overloading | Compile-time (reference type) | Same operation, different inputs |
| Overriding | Runtime (object type) | Polymorphic behavior |
| Upcasting | Implicit | Treat child as parent |
| Downcasting | Explicit + instanceof | Access child-specific features |
| instanceof | Runtime check | Safe downcasting |