Advanced⏱️ 9 min📘 Topic 12 of 22

🎭 Polymorphism in Java — Dynamic Dispatch and Upcasting

Master polymorphism in Java — runtime vs compile-time polymorphism, dynamic method dispatch, upcasting/downcasting, virtual methods and the role of @Override. With examples.

Polymorphism = 'many forms'. One reference type, many runtime implementations. It's what makes OOP flexible.

🔀 Two kinds

  • Compile-time (static) — method overloading; the compiler picks the method by argument types.
  • Runtime (dynamic) — method overriding; the JVM picks the method by the object's actual type.

⚙️ Dynamic method dispatch

A superclass reference can point to a subclass object. When you call an overridden method, the JVM dispatches to the actual object's implementation, not the reference type's:

Animal a = new Dog();  // upcasting
a.speak();             // calls Dog.speak() at runtime

In Java, all non-static, non-final, non-private methods are 'virtual' — overridable and dynamically dispatched by default.

⬆️⬇️ Upcasting & downcasting

  • Upcast (subclass → superclass) — always safe, implicit.
  • Downcast (superclass → subclass) — explicit, can throw ClassCastException; guard with instanceof.

💡 The power

Write code against the abstraction (Animal, List, Shape) and it works with any current or future implementation. This is the basis of clean, extensible design.

💻 Code Examples

Dynamic dispatch through a superclass reference

class Shape { double area() { return 0; } }
class Circle extends Shape {
  double r;
  Circle(double r){ this.r = r; }
  @Override double area() { return Math.PI * r * r; }
}
Shape s = new Circle(2);   // upcast
System.out.printf("%.2f%n", s.area()); // Circle.area()
Output:
12.57

Polymorphic loop over a list

List<Shape> shapes = List.of(new Circle(1), new Circle(2));
double total = 0;
for (Shape sh : shapes) total += sh.area();
System.out.printf("%.2f%n", total);
Output:
15.71

Safe downcast with instanceof

Shape s = new Circle(3);
if (s instanceof Circle c) {
  System.out.println(c.r);
}
Output:
3.0

⚠️ Common Mistakes

  • Downcasting without an instanceof check — throws ClassCastException at runtime.
  • Expecting field access to be polymorphic — only methods dispatch dynamically; fields are resolved by reference type.
  • Marking a method final or private and expecting it to be overridable — those aren't virtual.
  • Confusing overloading (compile-time) with overriding (runtime) polymorphism.

🎯 Interview Questions

Real questions asked at top product and service-based companies.

Q1.What is polymorphism?Beginner
The ability of one reference type to refer to objects of many implementing/derived types, so a single call invokes different behavior depending on the actual object. Java has compile-time (overloading) and runtime (overriding) polymorphism.
Q2.What is dynamic method dispatch?Intermediate
At runtime, the JVM selects the overridden method based on the object's actual class, not the reference type. A superclass reference pointing to a subclass object calls the subclass's overridden method.
Q3.Are Java fields polymorphic?Intermediate
No. Only methods are dynamically dispatched. Field access is resolved at compile time using the reference's declared type, which can surprise people who shadow a field in a subclass.
Q4.What's the difference between upcasting and downcasting?Advanced
Upcasting (subclass to superclass) is implicit and always safe. Downcasting (superclass to subclass) is explicit and can throw ClassCastException if the object isn't actually that subtype — guard with instanceof.
Q5.Which methods are NOT polymorphic in Java?Advanced
static, final, and private methods are bound at compile time (static binding) and are not overridden/dynamically dispatched. All other instance methods are virtual by default.

🧠 Quick Summary

  • Polymorphism = one reference type, many runtime forms.
  • Overloading = compile-time; overriding = runtime polymorphism.
  • Dynamic dispatch picks the method by the object's actual type.
  • Fields are NOT polymorphic; static/final/private aren't virtual.
  • Upcast is safe and implicit; downcast needs instanceof.