1. Introduction
Polymorphism, meaning 'many forms,' allows a single interface or method name to behave differently depending on the context. In Java, polymorphism appears in two forms: compile-time (static) polymorphism, achieved through method overloading, and runtime (dynamic) polymorphism, achieved through method overriding.
Cricket analogy: Polymorphism means 'many forms', like how a bowler's role changes shape—a spinner chosen before the match based on the pitch (compile-time, like overloading) versus a bowler actually adjusting their line mid-over based on the batsman facing them (runtime, like overriding).
Runtime polymorphism is closely tied to inheritance: a superclass reference variable can hold a reference to a subclass object, and the actual method executed is determined at runtime based on the object's real type — this is called dynamic method dispatch.
Cricket analogy: Runtime polymorphism is like an 'All-Rounder' contract slot on a team sheet that can actually hold a specific player like Ravindra Jadeja or Hardik Pandya, and which player's actual bowling action executes is decided live during the over — dynamic method dispatch on the field.
2. Syntax
// Compile-time polymorphism: method overloading
class Calc {
int add(int a, int b) { return a + b; }
double add(double a, double b) { return a + b; }
}
// Runtime polymorphism: method overriding
class Animal {
void sound() { System.out.println("Animal makes a sound"); }
}
class Dog extends Animal {
@Override
void sound() { System.out.println("Dog barks"); }
}
// Upcasting
Animal a = new Dog();
a.sound(); // Dog's overridden version runs (dynamic dispatch)3. Explanation
Method overloading is resolved at compile time based on the method signature (number/type of parameters) — the compiler decides which version to call. Method overriding, however, is resolved at runtime: the JVM looks at the actual object type (not the reference type) to decide which overridden method implementation to execute, a process known as dynamic method dispatch.
Cricket analogy: Overloading is the selectors picking a squad before the tour based on the player names on the list (compile-time, resolved by signature), while overriding is the umpire's actual lbw decision made live based on the real trajectory of the ball — dynamic method dispatch at runtime.
Upcasting (assigning a subclass object to a superclass reference, e.g., Animal a = new Dog();) is implicit and safe. Downcasting (converting a superclass reference back to a subclass type, e.g., Dog d = (Dog) a;) must be explicit and can throw a ClassCastException at runtime if the object is not actually an instance of the target subclass.
Cricket analogy: Upcasting is like listing a specialist spinner simply as 'Bowler' on the scoreboard—safe and automatic—while downcasting is explicitly claiming that generic 'Bowler' is specifically the spinner Kuldeep Yadav, which fails with a ClassCastException-like error if it's actually a pacer.
Exam trap: With overriding, the method that runs depends on the object's actual runtime type, NOT the reference type. With overloading, the version called depends on the reference/compile-time argument types.
4. Example
public class Shape {
void draw() {
System.out.println("Drawing a generic shape");
}
}
class Circle extends Shape {
@Override
void draw() {
System.out.println("Drawing a circle");
}
}
class Square extends Shape {
@Override
void draw() {
System.out.println("Drawing a square");
}
}
class Demo {
public static void main(String[] args) {
Shape[] shapes = { new Circle(), new Square(), new Shape() };
for (Shape s : shapes) {
s.draw(); // runtime polymorphism via dynamic dispatch
}
// downcasting example
Shape s = new Circle();
Circle c = (Circle) s; // safe: s actually refers to a Circle
c.draw();
}
}5. Output
Drawing a circle
Drawing a square
Drawing a generic shape
Drawing a circle6. Key Takeaways
- Compile-time polymorphism (method overloading) is resolved by the compiler based on method signature.
- Runtime polymorphism (method overriding) is resolved by the JVM based on the object's actual type.
- A superclass reference can hold a subclass object (upcasting), enabling dynamic dispatch.
- Downcasting must be explicit and can throw ClassCastException if the types are incompatible.
- Dynamic method dispatch means the overridden method of the actual object runs, not the reference's declared type.
Practice what you learned
1. Method overloading is an example of which type of polymorphism?
2. What determines which overridden method executes at runtime?
3. What is upcasting?
4. What can happen during an invalid downcast?
5. In the Shape example, why does new Shape().draw() print 'Drawing a generic shape'?
Was this page helpful?
You May Also Like
Inheritance in Java
Understand Java inheritance using the extends keyword, the super keyword, single inheritance rules, and the Object root class.
Abstraction in Java
Understand abstraction in Java through abstract classes and interfaces, and how they hide implementation details from the user.
Encapsulation in Java
Learn how encapsulation in Java bundles data and behavior together using private fields, access modifiers, and getters/setters.
Related Reading
Related Study Notes in Programming
Browse all study notesApache Spark Study Notes
Programming · 30 topics
ProgrammingApache Flink Study Notes
Programming · 30 topics
ProgrammingHadoop Study Notes
Programming · 30 topics
ProgrammingSnowflake Study Notes
Programming · 30 topics
ProgrammingApache Airflow Study Notes
Programming · 30 topics
Programmingdbt (Data Build Tool) Study Notes
Programming · 30 topics