What is Polymorphism in Java?
Polymorphism means "many forms." In Java, it refers to the ability of a single function, operator, or object to behave differently based on the context. It's one of the key principles of Object-Oriented Programming (OOPs).
Real-world example: A person can be a father, a husband, an employee — all at the same time (many forms). Similarly, in Java, a single method can have multiple implementations (method overriding) or multiple methods can share the same name (method overloading).
Types of Polymorphism in Java
Achieved by method overloading. Allows multiple methods with the same name but different parameters (different number, types, or order) in a class. Resolved at compile-time.
Achieved by method overriding. The call to an overridden method is resolved at runtime, allowing dynamic behavior based on the actual object type.
Objectives of Polymorphism
🔹 Code Flexibility: The main objective of polymorphism is to enhance code flexibility and reusability.
🔹 Dynamic Method Execution: It supports method overriding and overloading in classes, enabling dynamic method execution at runtime.
🔹 Code Reusability: Reduces code duplication by allowing multiple implementations under a common interface.
Advantages of Polymorphism
🔧 Maintainability: Polymorphism improves code maintainability by allowing a single interface to handle different data types.
🔄 Dynamic Behavior: It supports dynamic behavior in applications (runtime polymorphism with method overriding).
🔌 Loose Coupling: Encourages loose coupling between classes, making the code easier to extend and manage.
📦 Code Reusability: Promotes code reuse by allowing developers to write more generic and flexible code.
Compile-time vs Runtime Polymorphism
| Aspect | Compile-time Polymorphism | Runtime Polymorphism |
|---|---|---|
| Also known as | Static Binding / Early Binding | Dynamic Binding / Late Binding |
| Achieved by | Method Overloading | Method Overriding |
| When resolved | At compile-time | At runtime |
| Speed | Faster (no runtime overhead) | Slightly slower (method lookup at runtime) |
| Keyword used | Method name reuse | @Override annotation |
Method Overriding (Runtime Polymorphism)
// Parent class
class Animal {
void sound() {
System.out.println("Animal makes a sound");
}
}
// Child class
class Dog extends Animal {
@Override
void sound() {
System.out.println("Dog barks");
}
}
// Another child class
class Cat extends Animal {
@Override
void sound() {
System.out.println("Cat meows");
}
}
// Main class demonstrating runtime polymorphism
public class RuntimePolymorphismExample {
public static void main(String[] args) {
Animal myAnimal; // reference variable of Animal
myAnimal = new Dog();
myAnimal.sound(); // Output: Dog barks
myAnimal = new Cat();
myAnimal.sound(); // Output: Cat meows
}
}
Method Overloading (Compile-time Polymorphism)
class Calculator {
// Method with two int parameters
int add(int a, int b) {
return a + b;
}
// Overloaded method with three int parameters
int add(int a, int b, int c) {
return a + b + c;
}
// Overloaded method with double parameters
double add(double a, double b) {
return a + b;
}
// Overloaded method with different order of parameters
int add(int a, double b) {
return a + (int)b;
}
}
public class CompileTimePolymorphismExample {
public static void main(String[] args) {
Calculator calc = new Calculator();
System.out.println("Sum (2 ints): " + calc.add(5, 10));
System.out.println("Sum (3 ints): " + calc.add(5, 10, 15));
System.out.println("Sum (2 doubles): " + calc.add(5.5, 3.2));
System.out.println("Sum (int, double): " + calc.add(5, 3.2));
}
}
Polymorphism Using Interfaces
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 InterfacePolymorphismExample {
public static void main(String[] args) {
Drawable d; // Interface reference
d = new Circle();
d.draw(); // Output: Drawing Circle
d = new Rectangle();
d.draw(); // Output: Drawing Rectangle
}
}
Best Practices for Polymorphism
Use polymorphism to write more generic and reusable code
Always use the @Override annotation when overriding methods
Prefer method overriding (runtime polymorphism) for extensible frameworks
Use method overloading for convenience methods (e.g., multiple parameter options)
Follow consistent naming conventions for overloaded methods
Avoid using method overloading with the same number of parameters but different return types (compiler error)