Polymorphism in Java is a core object-oriented programming (OOP) concept that allows objects to take multiple forms. Derived from Greek (poly = many, morph = form), it enables methods to perform different tasks based on the object’s type. In this guide, you’ll learn how to implement polymorphism using method overriding, dynamic binding, and interfaces, along with practical examples and best practices.
Table of Contents
What is Polymorphism?
Polymorphism lets a single method or class behave differently based on context. It simplifies code by allowing:
- Dynamic method invocation (runtime polymorphism).
- Method overloading (compile-time polymorphism).
Real-World Example:
A printer can print documents, photos, or 3D models—same action (print), different implementations.
Types of Polymorphism in Java
1. Runtime Polymorphism (Method Overriding)
Achieved by method overriding in inheritance hierarchies. The JVM decides which method to execute at runtime.
Example:
class Animal {
void sound() {
System.out.println("Animal makes a sound");
}
}
class Dog extends Animal {
@Override
void sound() {
System.out.println("Dog barks");
}
}
class Cat extends Animal {
@Override
void sound() {
System.out.println("Cat meows");
}
}
public class Main {
public static void main(String[] args) {
Animal myDog = new Dog(); // Upcasting
Animal myCat = new Cat();
myDog.sound(); // Output: Dog barks
myCat.sound(); // Output: Cat meows
}
}
Key Points:
- Use
@Override
annotation to ensure correct overriding. - The reference type (e.g.,
Animal
) and object type (e.g.,Dog
) determine executed methods.
2. Compile-Time Polymorphism (Method Overloading)
Achieved by defining multiple methods with the same name but different parameters in the same class.
Example:
class Calculator {
// Add two integers
int add(int a, int b) {
return a + b;
}
// Add three integers
int add(int a, int b, int c) {
return a + b + c;
}
// Add two doubles
double add(double a, double b) {
return a + b;
}
}
public class Main {
public static void main(String[] args) {
Calculator calc = new Calculator();
System.out.println(calc.add(5, 10)); // 15
System.out.println(calc.add(5.5, 2.3)); // 7.8
}
}
Key Points:
- Overloaded methods must have different parameter types, count, or order.
- Return type alone doesn’t differentiate methods.
Runtime vs. Compile-Time Polymorphism
Runtime (Method Overriding) | Compile-Time (Method Overloading) |
---|---|
Resolved at runtime (dynamic binding). | Resolved at compile time (static binding). |
Requires inheritance. | Occurs in the same class. |
Example: Animal dog = new Dog(); | Example: add(int a, int b) vs. add(double a, double b) |
Why Use Polymorphism?
- Code Flexibility: Write generic code that works with multiple object types.
- Maintainability: Isolate changes to specific classes.
- Reusability: Share method names across related classes.
- Scalability: Extend functionality without modifying existing code.
Polymorphism with Interfaces
Interfaces enable polymorphism by defining contracts that multiple classes can implement differently.
Example:
interface Shape {
double calculateArea();
}
class Circle implements Shape {
private double radius;
Circle(double radius) { this.radius = radius; }
@Override
public double calculateArea() {
return Math.PI * radius * radius;
}
}
class Square implements Shape {
private double side;
Square(double side) { this.side = side; }
@Override
public double calculateArea() {
return side * side;
}
}
public class Main {
public static void main(String[] args) {
Shape circle = new Circle(5);
Shape square = new Square(4);
System.out.println("Circle Area: " + circle.calculateArea()); // 78.54
System.out.println("Square Area: " + square.calculateArea()); // 16.0
}
}
Common Pitfalls (and Fixes)
1. Accidental Method Hiding (Static Methods)
Mistake:
class Parent {
static void display() { System.out.println("Parent"); }
}
class Child extends Parent {
static void display() { System.out.println("Child"); } // Hiding, not overriding
}
Fix: Use instance methods for overriding, not static
methods.
2. Incorrect Overloading Signatures
Mistake:
int add(int a, int b) { ... }
double add(int a, int b) { ... } // Compile error (same parameters)
Fix: Change parameter types, count, or order.
Best Practices
- Use
@Override
: Avoid typos and ensure correct overriding. - Favor Interfaces for Polymorphism: Decouple code from implementations.
- Avoid Overloading with Ambiguous Parameters: Prevent confusion in method calls.
- Leverage Dynamic Binding: Design extensible systems using inheritance.
Conclusion
Polymorphism in Java empowers you to write adaptable, scalable code. By mastering method overriding, overloading, and interface-driven design, you’ll build systems that gracefully evolve with changing requirements.
FAQs
Can we override static methods?
No. Static methods are bound at compile time (no runtime polymorphism).
What is the difference between overriding and overloading?
Overriding: Same method signature in subclass (runtime).
Overloading: Same method name, different parameters (compile time).
Can constructors be overridden?
No. Constructors are not inherited.