Polymorphism in Java
1. Introduction
Polymorphism is one of the four fundamental principles of Object-Oriented Programming (OOP) along with Inheritance, Encapsulation, and Abstraction. The word "Polymorphism" is derived from Greek, meaning "many forms." In Java, polymorphism allows an object to behave in multiple ways depending on the context.
2. Why Use Polymorphism?
- Improves code readability and reusability.
- Supports dynamic method invocation.
- Enhances flexibility and maintainability.
- Allows the same interface to be used for different underlying forms (data types).
3. Types of Polymorphism in Java
Java supports two types of polymorphism:
- Compile-Time Polymorphism (Static Binding or Method Overloading)
- Run-Time Polymorphism (Dynamic Binding or Method Overriding)
4. Compile-Time Polymorphism (Static Binding or Method Overloading)
Definition:
Compile-time polymorphism is resolved during the compilation of the program. It occurs when multiple methods in the same class have the same name but different parameters (method signature). This is called method overloading.
How it works:
- Method resolution happens at compile time.
- The compiler decides which method to invoke based on the method signature (i.e., number, type, and order of parameters).
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;
}
}
public class Main {
public static void main(String[] args) {
Calculator calc = new Calculator();
System.out.println(calc.add(5, 10)); // Calls int add(int, int)
System.out.println(calc.add(5.5, 4.5)); // Calls double add(double, double)
System.out.println(calc.add(1, 2, 3)); // Calls int add(int, int, int)
}
}
Output:
15
10.0
6
Key Points:
- All methods must be in the same class.
- Method name is same, but parameter list must be different.
- Return type can be same or different.
- Does not depend on inheritance.
5. Run-Time Polymorphism (Dynamic Binding or Method Overriding)
Definition:
Run-time polymorphism occurs when a subclass provides a specific implementation of a method that is already defined in its superclass. This is known as method overriding.
How it works:
- Method resolution happens at runtime.
- The method that gets executed is based on the object type, not the reference type.
- Achieved through inheritance and method overriding.
Example:
class Animal {
void sound() {
System.out.println("Animal makes a sound");
}
}
class Dog extends Animal {
void sound() {
System.out.println("Dog barks");
}
}
class Cat extends Animal {
void sound() {
System.out.println("Cat meows");
}
}
public class Main {
public static void main(String[] args) {
Animal a;
a = new Dog();
a.sound(); // Dog's sound()
a = new Cat();
a.sound(); // Cat's sound()
}
}
Output:
Dog barks
Cat meows
Key Points:
- Requires inheritance and method overriding.
- The method to be executed is determined by the object, not the reference.
- Achieved using dynamic method dispatch.
- Method signature must be same in parent and child.
- Only instance methods can be overridden; static methods cannot be overridden, they are hidden.
6. Method Overloading vs Method Overriding

Feature | Method Overloading | Method Overriding |
---|---|---|
Definition | Same method name, different parameters | Same method name and parameters in subclass |
Occurs in | Same class | Different classes (Inheritance required) |
Binding Time | Compile-time | Run-time |
Return Type | Can be different | Must be same or covariant |
Access Modifier | Any | Can't reduce visibility |
Note: Covariant Return Type
A covariant return type means that in method overriding, the return type of the overridden method in the subclass can be a subclass of the return type declared in the parent class.
This allows more specific return types in the subclass while maintaining polymorphism.
Example:
class Animal {
Animal getAnimal() {
return new Animal();
}
}
class Dog extends Animal {
@Override
Dog getAnimal() { // Covariant return type
return new Dog();
}
}
Here:
- getAnimal() in Animal returns Animal.
- getAnimal() in Dog overrides it but returns Dog (a subclass of Animal).
This is valid in Java and is called covariant return type.
7. Rules for Method Overriding
- Method must have the same name, return type, and parameters.
- Subclass method cannot have a more restrictive access modifier.
- Only inherited methods can be overridden.
- Constructors cannot be overridden.
- The overriding method can throw fewer or narrower checked exceptions.
8. Dynamic Method Dispatch
Definition:
Dynamic Method Dispatch is the mechanism by which a call to an overridden method is resolved at runtime rather than at compile time. This is the foundation of runtime polymorphism in Java.
It allows Java to decide which version of the overridden method to call based on the object that the reference points to — not the reference type itself.
Key Concepts:
- Occurs only with overridden methods (not overloaded).
- Requires inheritance and method overriding.
- The reference type is of a superclass, and the object type is of a subclass.
Why it's useful:
Dynamic method dispatch enables you to write general code that can work with different types of objects at runtime, increasing flexibility and scalability.
Example Recap:
class Animal {
void sound() {
System.out.println("Animal makes a sound");
}
}
class Dog extends Animal {
void sound() {
System.out.println("Dog barks");
}
}
public class Test {
public static void main(String[] args) {
Animal a = new Dog(); // superclass reference, subclass object
a.sound(); // calls Dog's sound() method
}
}
Output:
Dog barks
Although a is of type Animal, the method sound() of class Dog is called — this decision is made at runtime, not compile time.
9. Real-World Example of Polymorphism
This example demonstrates how polymorphism is useful in real-world scenarios, like in a company with different types of employees.
Code:
abstract class Employee {
abstract void work();
}
class Manager extends Employee {
void work() {
System.out.println("Managing team");
}
}
class Developer extends Employee {
void work() {
System.out.println("Writing code");
}
}
public class Company {
public static void main(String[] args) {
Employee e1 = new Manager();
Employee e2 = new Developer();
e1.work();
e2.work();
}
}
Output:
Managing team
Writing code
Explanation:
- Employee is an abstract class with an abstract method work().
- Both Manager and Developer extend Employee and override the work() method.
- In the main() method:
- e1 is a reference of type Employee pointing to an object of type Manager.
- e2 is a reference of type Employee pointing to an object of type Developer.
At runtime:
- e1.work() calls Manager's version of work().
- e2.work() calls Developer's version of work().
What It Demonstrates:
- The same reference type (Employee) can point to different object types (Manager, Developer).
- This is polymorphism — one interface, many implementations.
- It promotes code flexibility, scalability, and maintenance.
10. Polymorphism with Interfaces
Polymorphism with interfaces occurs when a single interface type is used to refer to objects of different classes that implement that interface.
This allows you to invoke the same method on different objects and achieve different behaviors based on the actual object type at runtime.
This is a classic example of runtime polymorphism using interfaces rather than abstract or base classes.
Example Code:
interface Drawable {
void draw();
}
class Circle implements Drawable {
public void draw() {
System.out.println("Drawing Circle");
}
}
class Rectangle implements Drawable {
public void draw() {
System.out.println("Drawing Rectangle");
}
}
public class Test {
public static void main(String[] args) {
Drawable d1 = new Circle();
Drawable d2 = new Rectangle();
d1.draw();
d2.draw();
}
}
Output:
Drawing Circle
Drawing Rectangle
Explanation:
Interface Declaration:
interface Drawable { void draw(); }
- Drawable is an interface with one method draw().
- Any class that implements Drawable must provide an implementation for draw().
- Class Implementations:
- Circle and Rectangle both implement the Drawable interface.
- Each class provides its own version of the draw() method.
Polymorphic Behavior:
Drawable d1 = new Circle(); Drawable d2 = new Rectangle();
- Here, d1 and d2 are both of type Drawable (the interface), but they point to objects of different classes (Circle and Rectangle).
- When you call d1.draw() and d2.draw(), the method that gets executed depends on the actual object type, not the reference type.
- This is dynamic method dispatch at work — and it’s an example of interface-based polymorphism.
Why Use Interface Polymorphism?
- Promotes flexibility and loose coupling.
- Helps in writing scalable and maintainable code.
- Allows methods and systems to work with objects of different classes through a common interface.
- Makes it easy to switch or extend functionality — just create new classes implementing the same interface.
Real-World Analogy:
Imagine Drawable as a common remote control.
Different devices (like TV, AC, Music System) can have their own behavior for the powerOn() method.
You just need to use the same interface, and the specific device will perform its own action.
Let me know if you'd like a visual summary or a table comparing interface vs abstract class polymorphism.
11. Advantages of Polymorphism
- Flexibility: Same interface can be used for different implementations.
- Maintainability: Easier to update or replace components.
- Scalability: New behaviors can be introduced with minimal changes.
- Reusability: Generalized code works for multiple object types.
12. Disadvantages of Polymorphism
- Can be complex to debug due to dynamic method dispatch.
- Increases indirectness, making code harder to trace.
- Misuse can lead to unexpected behavior if not properly overridden.
Conclusion
Polymorphism in Java is a powerful concept that enhances flexibility and scalability in object-oriented programming. It allows objects to take many forms, enabling a single interface to represent different underlying data types or behaviors. This leads to cleaner code, easier maintenance, and the ability to extend applications with minimal changes.
Through compile-time (method overloading) and runtime (method overriding) polymorphism, Java allows developers to write more general and reusable code, while still supporting specific implementations when needed. Polymorphism also plays a crucial role in achieving dynamic method dispatch, which is key to implementing runtime flexibility in object-oriented systems.
In short, mastering polymorphism empowers Java developers to write code that is adaptable, modular, and easier to maintain—an essential skill for building robust and scalable software.