Polymorphism
Polymorphism is one of the four fundamental principles of Object-Oriented Programming (OOP) in Java, alongside encapsulation, inheritance, and abstraction. The term "polymorphism" is derived from the Greek words poly (many) and morph (forms), meaning "many forms." In Java, polymorphism allows a single entity (such as a method or object) to behave differently based on the context.
Polymorphism enhances code flexibility, reusability, and maintainability by allowing developers to write more generic and extensible code. It is primarily achieved through method overloading (compile-time polymorphism) and method overriding (runtime polymorphism).
Types of Polymorphism in Java
1. Compile-Time Polymorphism (Method Overloading)
Compile-time polymorphism, also known as static polymorphism, occurs when multiple methods in the same class share the same name but differ in their parameter lists (number, type, or order of parameters). The method to be called is determined at compile time based on the method signature.
Example:
class Calculator {
// Method with two parameters
int add(int a, int b) {
return a + b;
}
// Overloaded method with three parameters
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)); // Output: 15
System.out.println(calc.add(5, 10, 20)); // Output: 35
}
}
In this example, the add method is overloaded with two versions: one that takes two parameters and another that takes three. The appropriate method is selected at compile time based on the arguments passed.
2. Runtime Polymorphism (Method Overriding)
Runtime polymorphism, also known as dynamic polymorphism, occurs when a subclass provides a specific implementation of a method that is already defined in its parent class. The method to be called is determined at runtime based on the object's actual type.
Example:
class Animal {
void makeSound() {
System.out.println("Some generic animal sound");
}
}
class Dog extends Animal {
@Override
void makeSound() {
System.out.println("The dog barks");
}
}
class Cat extends Animal {
@Override
void makeSound() {
System.out.println("The cat meows");
}
}
public class Main {
public static void main(String[] args) {
Animal myAnimal;
myAnimal = new Dog();
myAnimal.makeSound(); // Output: The dog barks
myAnimal = new Cat();
myAnimal.makeSound(); // Output: The cat meows
}
}
In this example, the makeSound method is overridden in the Dog and Cat classes. At runtime, the method corresponding to the actual object type (Dog or Cat) is executed, even though the reference type is Animal.
Polymorphism with Interfaces
Polymorphism can also be achieved using interfaces. A class can implement multiple interfaces, and the behavior can vary depending on the implementation.
Example:
interface Shape {
void draw();
}
class Circle implements Shape {
@Override
public void draw() {
System.out.println("Drawing a circle");
}
}
class Rectangle implements Shape {
@Override
public void draw() {
System.out.println("Drawing a rectangle");
}
}
public class Main {
public static void main(String[] args) {
Shape shape;
shape = new Circle();
shape.draw(); // Output: Drawing a circle
shape = new Rectangle();
shape.draw(); // Output: Drawing a rectangle
}
}
Shape interface is implemented by Circle and Rectangle. The draw method behaves differently depending on the actual object type.Key Differences Between Compile-Time and Runtime Polymorphism
Feature | Compile-Time Polymorphism | Runtime Polymorphism |
|---|---|---|
Method Binding | Happens at compile time (static binding). | Happens at runtime (dynamic binding). |
Achieved Through | Method overloading. | Method overriding. |
Flexibility | Less flexible, as the method is fixed at compile time. | More flexible, as the method is determined at runtime. |
Inheritance Required | No. | Yes (or interface implementation). |
Advantages of Polymorphism
- Code Reusability: Polymorphism allows the same method or interface to be reused across different classes.
- Extensibility: New functionality can be added without modifying existing code.
- Flexibility: Objects can take on multiple forms, making the code more dynamic and adaptable.
- Simplified Code: Polymorphism reduces the need for complex conditional statements (e.g.,
if-elseorswitch).
Real-World Example of Polymorphism
Consider a payment system where different payment methods (credit card, PayPal, bank transfer) are implemented. Polymorphism allows you to handle all payment methods using a single interface or parent class.
package polymorph;
abstract class Payment {
abstract void pay(double amount);
}
class CreditCard extends Payment {
@Override
void pay(double amount) {
System.out.println("Paid $" + amount + " using Credit Card.");
}
}
class PayPal extends Payment {
@Override
void pay(double amount) {
System.out.println("Paid $" + amount + " using PayPal.");
}
}
package polymorph;
public class MainRunner {
public static void main(String[] args) {
Payment payment;
payment = new CreditCard();
payment.pay(100.0); // Output: Paid $100.0 using Credit Card.
payment = new PayPal();
payment.pay(200.0); // Output: Paid $200.0 using PayPal.
}
}
