Polymorphism
java polymorphism method overloading overriding dynamic method dispatch upcasting downcasting instanceof covariant return compile-time runtime
Introduction
In the previous lesson, you learned how Inheritance allows one class to inherit properties and behavior from another class.
However, inheritance alone is not enough to build flexible and scalable enterprise applications.
Imagine a payment application:
- Credit Card Payment
- UPI Payment
- Net Banking
- PayPal Payment
Each payment method processes payments differently.
Instead of writing separate code for every payment type, Java allows us to write one interface and multiple implementations.
This concept is called Polymorphism.
The word Polymorphism comes from two Greek words:
- Poly → Many
- Morph → Forms
Meaning:
One interface, many implementations.
Polymorphism is one of the four pillars of Object-Oriented Programming (OOP) and is heavily used in:
- Spring Boot
- Hibernate
- Java Collections Framework
- REST APIs
- Microservices
- Dependency Injection
- Design Patterns
- Enterprise Applications
In this lesson, you'll learn:
- What is Polymorphism?
- Types of Polymorphism
- Compile-Time Polymorphism
- Runtime Polymorphism
- Method Overloading
- Method Overriding
- Dynamic Method Dispatch
- Upcasting
- Downcasting
instanceofOperator- Covariant Return Types
- Real-world Examples
- JVM Method Resolution
- Best Practices
- Common Mistakes
- Hands-on Exercises
- Professional Interview Questions
What is Polymorphism?
Polymorphism allows the same method call to behave differently depending on the object executing it.
Example:
Animal↓Dog↓Cat
Calling:
animal.sound();
Produces:
Dog:
Bark
Cat:
Meow
Same method.
Different behavior.
Why Polymorphism?
Without polymorphism:
if(type.equals("DOG")) {}else if(type.equals("CAT")) {}else if(type.equals("HORSE")) {}
With polymorphism:
animal.sound();
The JVM automatically calls the correct implementation.
Benefits:
- Loose Coupling
- Extensibility
- Clean Code
- Better Maintainability
- Easy Testing
- Supports Dependency Injection
Types of Polymorphism
Java supports two types:
1. Compile-Time Polymorphism
Also called:
- Static Polymorphism
- Early Binding
Achieved using:
- Method Overloading
2. Runtime Polymorphism
Also called:
- Dynamic Polymorphism
- Late Binding
Achieved using:
- Method Overriding
Compile-Time Polymorphism
Method selection happens during compilation.
Example:
class Calculator {int add(int a, int b) {return a + b;}double add(double a, double b) {return a + b;}int add(int a, int b, int c) {return a + b + c;}}
Calling:
Calculator calc = new Calculator();System.out.println(calc.add(5, 10));System.out.println(calc.add(5.5, 3.2));System.out.println(calc.add(1,2,3));
Output:
158.76
The compiler decides which method to call.
Runtime Polymorphism
Method selection happens while the program is running.
Example:
class Animal {void sound() {System.out.println("Animal Sound");}}class Dog extends Animal {@Overridevoid sound() {System.out.println("Dog Barks");}}
Calling:
Animal animal = new Dog();animal.sound();
Output:
Dog Barks
Even though the reference is Animal, the JVM invokes the Dog implementation.
Dynamic Method Dispatch
Dynamic Method Dispatch is the process by which the JVM decides which overridden method to execute at runtime.
Example:
Animal animal;animal = new Dog();animal.sound();animal = new Cat();animal.sound();
Output:
Dog BarksCat Meows
Decision happens during runtime based on the actual object.
Upcasting
Upcasting means assigning a child object to a parent reference.
Example:
Dog dog = new Dog();Animal animal = dog;
Or directly:
Animal animal = new Dog();
Advantages:
- Runtime Polymorphism
- Loose Coupling
- Flexible Design
Downcasting
Downcasting converts a parent reference back to a child reference.
Example:
Animal animal = new Dog();Dog dog = (Dog) animal;
Now child-specific methods become accessible.
Example:
dog.fetch();
Use downcasting carefully because an invalid cast throws a ClassCastException.
The instanceof Operator
Before downcasting, verify the object's type.
Example:
Animal animal = new Dog();if (animal instanceof Dog) {Dog dog = (Dog) animal;dog.fetch();}
This prevents invalid type casts.
Covariant Return Types
Java allows an overriding method to return a more specific type.
Example:
class Animal {Animal create() {return new Animal();}}class Dog extends Animal {@OverrideDog create() {return new Dog();}}
This is called a covariant return type.
JVM Method Resolution
For overridden methods:
Reference Type↓Actual Object↓Find Overridden Method↓Execute Child Version
The JVM uses the object's actual type, not the reference type.
Method Overloading vs Method Overriding
| Method Overloading | Method Overriding |
|---|---|
| Same class | Parent & Child |
| Different parameters | Same parameters |
| Compile-time | Runtime |
| Static binding | Dynamic binding |
| Improves readability | Enables polymorphism |
Real-World Example: Payment Gateway
class Payment {void pay(double amount) {System.out.println("Processing Payment");}}class UpiPayment extends Payment {@Overridevoid pay(double amount) {System.out.println("Processing UPI Payment");}}class CardPayment extends Payment {@Overridevoid pay(double amount) {System.out.println("Processing Card Payment");}}
Using polymorphism:
Payment payment = new UpiPayment();payment.pay(1000);payment = new CardPayment();payment.pay(2500);
Output:
Processing UPI PaymentProcessing Card Payment
Real-World Example: Notification System
class Notification {void send() {System.out.println("Sending Notification");}}class EmailNotification extends Notification {@Overridevoid send() {System.out.println("Sending Email");}}class SmsNotification extends Notification {@Overridevoid send() {System.out.println("Sending SMS");}}
Memory Representation
Animal animal = new Dog();
Memory:
Stackanimal↓Heap+-------------------------+| Animal Fields || Dog Fields |+-------------------------+
Reference Type:
Animal
Actual Object:
Dog
Method execution depends on the actual object.
Best Practices
Program to Interfaces, Not Implementations
Prefer:
List<String> names = new ArrayList<>();
Instead of:
ArrayList<String> names = new ArrayList<>();
This makes code easier to extend and test.
Use @Override
Always annotate overridden methods to allow the compiler to verify correctness.
Minimize Downcasting
Frequent downcasting often indicates a design issue.
Use Polymorphism to Eliminate Long if-else Chains
Replace conditional logic with polymorphic behavior where appropriate.
Prefer Composition When Needed
Inheritance and polymorphism should represent genuine IS-A relationships.
Common Mistakes
Confusing Overloading with Overriding
Overloading is resolved at compile time.
Overriding is resolved at runtime.
Forgetting @Override
Without the annotation, a spelling mistake can create a new method instead of overriding the parent method.
Unsafe Downcasting
Casting an object to an incompatible type causes ClassCastException.
Assuming Fields are Polymorphic
Methods are polymorphic; fields are resolved based on the reference type.
Overusing Inheritance
Use interfaces or composition if inheritance does not model an IS-A relationship.
Hands-on Exercise
Create a Java program that:
- Creates an
Animalsuperclass with asound()method. - Creates
Dog,Cat, andCowclasses that overridesound(). - Demonstrates runtime polymorphism using an
Animalreference. - Creates overloaded
calculate()methods in aCalculatorclass. - Demonstrates upcasting and safe downcasting.
- Uses
instanceofbefore downcasting. - Creates a payment processing system using polymorphism instead of if-else.
Summary
Polymorphism enables a single interface to support multiple implementations, making applications flexible, extensible, and easier to maintain. Compile-time polymorphism is achieved through method overloading, while runtime polymorphism relies on method overriding and dynamic method dispatch. These concepts are fundamental to enterprise Java frameworks and design patterns.
Key Takeaways
- Polymorphism means one interface, many implementations.
- Java supports compile-time and runtime polymorphism.
- Method overloading provides compile-time polymorphism.
- Method overriding provides runtime polymorphism.
- Dynamic method dispatch determines the correct overridden method at runtime.
- Upcasting enables flexible programming.
- Downcasting should be performed carefully using
instanceof. - Covariant return types allow overridden methods to return more specific types.
- Program to interfaces rather than concrete implementations.
Professional Interview Questions
1What is Polymorphism in Java?
Professional Answer
Polymorphism is an Object-Oriented Programming principle that allows a single interface or parent reference to represent objects of different subclasses. The same method call can produce different behaviors depending on the actual object at runtime, improving flexibility, extensibility, and maintainability.
Follow-up Questions
- What are the two types of polymorphism in Java?
- How does polymorphism reduce coupling?
Interview Tip: Remember the definition: One Interface → Multiple Implementations.
2What is the difference between Compile-Time and Runtime Polymorphism?
Professional Answer
Compile-time polymorphism is achieved through method overloading, where the compiler selects the appropriate method based on the method signature. Runtime polymorphism is achieved through method overriding, where the JVM determines the method implementation based on the actual object type during execution.
Follow-up Questions
- Which one uses dynamic method dispatch?
- Which one is also known as early binding?
Interview Tip: Overloading = Compile-Time. Overriding = Runtime.
3What is Dynamic Method Dispatch?
Professional Answer
Dynamic Method Dispatch is the JVM mechanism used to resolve overridden method calls at runtime. When a parent reference points to a child object, the JVM invokes the overridden method of the actual object rather than the reference type. This enables runtime polymorphism and is a key feature used throughout enterprise Java frameworks.
Follow-up Questions
- Is constructor invocation polymorphic?
- Are static methods dynamically dispatched?
Interview Tip: A common interview example is: Animal animal = new Dog(); animal.sound(); — the JVM executes Dog.sound() even though the reference type is Animal.