Chapter 12: Polymorphism
Polymorphism is one of the four pillars of OOP and literally means “many forms”. It allows one interface / method name to behave differently depending on the object that is calling it.
Imagine we’re sitting together in a quiet corner of your favorite Mumbai café — it’s evening, the rain is gently tapping outside, and I’m going to explain polymorphism like I’m teaching my best friend who’s just starting to feel the real beauty of Java.
We’ll go super slowly, with lots of real-life analogies, complete runnable programs, step-by-step breakdowns, tables, common mistakes with fixes, and plenty of examples you can copy-paste and run right now.
Let’s dive in!
1. What is Polymorphism? (The Big Idea)
Polymorphism allows us to use one method name (or reference type) to invoke different implementations based on the actual object type at runtime.
There are two main types of polymorphism in Java:
| Type | When it happens | Achieved by | Also called |
|---|---|---|---|
| Compile-time | At compile time | Method Overloading | Static / Early binding |
| Runtime | At runtime | Method Overriding | Dynamic / Late binding |
2. Compile-time Polymorphism (Method Overloading)
Method Overloading:
- Same method name
- Different parameters (number, type, or order)
- Happens inside the same class (or inherited)
- Compiler decides which method to call based on the arguments at compile time
Real-life analogy: A remote control has a “Power” button.
- If you press it once → TV turns on
- If you press it twice quickly → TV turns off Same button name → different actions based on how you use it.
Example 1: Overloading in a Calculator class
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
public class Calculator { // Overloaded add methods public int add(int a, int b) { System.out.println("Adding two integers"); return a + b; } public double add(double a, double b) { System.out.println("Adding two doubles"); return a + b; } public int add(int a, int b, int c) { System.out.println("Adding three integers"); return a + b + c; } public String add(String s1, String s2) { System.out.println("Concatenating strings"); return s1 + s2; } } |
Test it:
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
public class Main { public static void main(String[] args) { Calculator calc = new Calculator(); System.out.println(calc.add(5, 10)); // two ints System.out.println(calc.add(5.5, 10.5)); // two doubles System.out.println(calc.add(1, 2, 3)); // three ints System.out.println(calc.add("Hello ", "Webliance")); // strings } } |
Output:
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 |
Adding two integers 15 Adding two doubles 16.0 Adding three integers 6 Concatenating strings Hello Webliance |
Important: Return type does not matter for overloading — only parameters count!
3. Runtime Polymorphism (Method Overriding + Upcasting)
Method Overriding (already introduced in Chapter 11):
- Child class redefines a method that exists in the parent class
- Same name, same parameters, same return type (or covariant return)
- @Override annotation is recommended
Runtime polymorphism happens when:
- We create a parent reference pointing to a child object (called upcasting)
- We call an overridden method → Java decides at runtime which version to execute (based on actual object type)
Real-life analogy: A remote control says “Play”.
- If connected to a TV → plays video
- If connected to a music system → plays song Same button → different action depending on what device is actually connected.
Example 2: Classic Animal Sound Example
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
// Parent class public class Animal { public void sound() { System.out.println("Some generic animal sound..."); } } // Child 1 public class Dog extends Animal { @Override public void sound() { System.out.println("Woof Woof!"); } } // Child 2 public class Cat extends Animal { @Override public void sound() { System.out.println("Meow Meow!"); } } // Child 3 public class Cow extends Animal { @Override public void sound() { System.out.println("Moo Moo!"); } } |
Test Runtime Polymorphism (Upcasting):
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
public class Main { public static void main(String[] args) { // Upcasting: Parent reference → Child object Animal pet1 = new Dog(); // Upcast Animal pet2 = new Cat(); Animal pet3 = new Cow(); // Same method call → different behaviors! pet1.sound(); // Woof Woof! pet2.sound(); // Meow Meow! pet3.sound(); // Moo Moo! // Even in an array! Animal[] animals = {new Dog(), new Cat(), new Cow()}; for (Animal animal : animals) { animal.sound(); // Each calls its own version } } } |
Output:
|
0 1 2 3 4 5 6 7 8 9 10 11 |
Woof Woof! Meow Meow! Moo Moo! Woof Woof! Meow Meow! Moo Moo! |
4. Upcasting and Downcasting
Upcasting (safe, automatic):
- Child object is treated as Parent type
- Always allowed (no explicit cast needed)
|
0 1 2 3 4 5 6 |
Animal animal = new Dog(); // Upcasting — automatic |
Downcasting (dangerous, needs explicit cast):
- Treating a Parent reference back to Child type
- Only safe if the object is actually of that child type
|
0 1 2 3 4 5 6 7 8 9 10 11 |
Animal animal = new Dog(); // Upcast Dog dog = (Dog) animal; // Downcast — OK because it is actually a Dog // But this will throw ClassCastException at runtime! Animal animal2 = new Cat(); Dog wrong = (Dog) animal2; // ERROR! Cat cannot be cast to Dog |
Safe way to downcast — use instanceof:
|
0 1 2 3 4 5 6 7 8 9 10 |
Animal pet = new Dog(); if (pet instanceof Dog) { Dog d = (Dog) pet; // safe to use Dog-specific methods } |
5. Quick Recap Table (Your Cheat Sheet)
| Concept | How it Works | When Decided | Example |
|---|---|---|---|
| Compile-time Polymorphism | Method Overloading | Compile time | add(int,int) vs add(double,double) |
| Runtime Polymorphism | Method Overriding + Upcasting | Runtime | animal.sound() calls correct version |
| Upcasting | Parent reference → Child object | Automatic | Animal a = new Dog(); |
| Downcasting | Child reference ← Parent reference | Explicit cast | Dog d = (Dog) animal; |
6. Common Mistakes & Fixes
| Mistake | Problem | Fix |
|---|---|---|
| Using == instead of .equals() | Wrong comparison | Always .equals() for objects |
| Forgetting @Override | Risk of accidental overload | Add @Override — compiler warns |
| Downcasting without instanceof | ClassCastException at runtime | Check with instanceof first |
| Changing parameter type in override | Not overriding — overloading instead | Keep exact same signature |
| Overriding static methods | Hiding, not overriding | Static methods cannot be overridden |
7. Real-World Example: Payment System (Polymorphism in Action)
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
// Parent abstract class Payment { protected double amount; public Payment(double amount) { this.amount = amount; } public abstract void processPayment(); } // Child 1 class CreditCardPayment extends Payment { public CreditCardPayment(double amount) { super(amount); } @Override public void processPayment() { System.out.println("Processing ₹" + amount + " via Credit Card..."); System.out.println("Payment successful! (with 2% fee)"); } } // Child 2 class UPIPayment extends Payment { public UPIPayment(double amount) { super(amount); } @Override public void processPayment() { System.out.println("Processing ₹" + amount + " via UPI..."); System.out.println("Payment successful! (instant & free)"); } } |
Test:
|
0 1 2 3 4 5 6 7 8 9 10 |
Payment p1 = new CreditCardPayment(1500); Payment p2 = new UPIPayment(2500); p1.processPayment(); p2.processPayment(); |
Output:
|
0 1 2 3 4 5 6 7 8 9 |
Processing ₹1500.0 via Credit Card... Payment successful! (with 2% fee) Processing ₹2500.0 via UPI... Payment successful! (instant & free) |
Homework for You (Practice to Master!)
- Basic: Create Shape abstract class with draw() method. Create Circle, Rectangle, Triangle — override draw().
- Medium: Make an array of Animal objects (Dog, Cat, Cow) — loop and call sound().
- Advanced: Create BankAccount (deposit(), withdraw()) → SavingsAccount, CurrentAccount override withdraw() with different overdraft rules.
- Challenge: Fix this buggy code:
Java012345678Animal a = new Dog();a.sound(); // Worksa.bark(); // Compile error — why?
- Fun: Create a MediaPlayer interface with play() → implement in VideoPlayer, AudioPlayer, MusicPlayer.
You’re doing fantastic! Polymorphism is what makes Java code flexible, reusable, and elegant — now you can write code that works with any object in a family!
