Introduction
Polymorphism means “many forms”. One interface can have multiple implementations.
Greek: poly = many, morph = forms
Types of Polymorphism
-
Compile-time Polymorphism (Static)
- Method Overloading
- Operator Overloading (not in Java)
-
Runtime Polymorphism (Dynamic)
- Method Overriding
Compile-time Polymorphism
Method Overloading:
class Calculator {
int add(int a, int b) {
return a + b;
}
int add(int a, int b, int c) {
return a + b + c;
}
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(10, 20)); // Calls int version
System.out.println(calc.add(10, 20, 30)); // Calls 3-parameter version
System.out.println(calc.add(10.5, 20.5)); // Calls double version
}
}
Runtime Polymorphism
Method Overriding:
class Animal {
void sound() {
System.out.println("Animal makes 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 a; // Parent reference
a = new Animal();
a.sound(); // Animal makes sound
a = new Dog();
a.sound(); // Dog barks (runtime decision)
a = new Cat();
a.sound(); // Cat meows (runtime decision)
}
}
Dynamic Method Dispatch
Method called is determined at runtime based on object type, not reference type.
class Parent {
void display() {
System.out.println("Parent");
}
}
class Child extends Parent {
@Override
void display() {
System.out.println("Child");
}
}
public class Main {
public static void main(String[] args) {
Parent p = new Child(); // Parent reference, Child object
p.display(); // Output: Child (method of object, not reference)
}
}
Complete Example
class Shape {
void draw() {
System.out.println("Drawing shape");
}
double area() {
return 0;
}
}
class Circle extends Shape {
double radius;
Circle(double radius) {
this.radius = radius;
}
@Override
void draw() {
System.out.println("Drawing circle");
}
@Override
double area() {
return Math.PI * radius * radius;
}
}
class Rectangle extends Shape {
double length, width;
Rectangle(double length, double width) {
this.length = length;
this.width = width;
}
@Override
void draw() {
System.out.println("Drawing rectangle");
}
@Override
double area() {
return length * width;
}
}
public class Main {
public static void main(String[] args) {
Shape[] shapes = new Shape[3];
shapes[0] = new Shape();
shapes[1] = new Circle(5);
shapes[2] = new Rectangle(4, 6);
// Polymorphic behavior
for (Shape shape : shapes) {
shape.draw();
System.out.println("Area: " + shape.area());
System.out.println();
}
}
}
Output:
Drawing shape
Area: 0.0
Drawing circle
Area: 78.53981633974483
Drawing rectangle
Area: 24.0
Upcasting
Assigning subclass object to superclass reference.
class Animal { }
class Dog extends Animal { }
public class Main {
public static void main(String[] args) {
Dog dog = new Dog();
Animal animal = dog; // Upcasting (implicit)
// Or directly
Animal a = new Dog(); // Upcasting
}
}
Downcasting
Assigning superclass reference to subclass variable.
class Animal {
void eat() {
System.out.println("Eating");
}
}
class Dog extends Animal {
void bark() {
System.out.println("Barking");
}
}
public class Main {
public static void main(String[] args) {
Animal a = new Dog(); // Upcasting
// a.bark(); // ✗ Error: Animal reference can't access Dog methods
Dog d = (Dog) a; // Downcasting (explicit cast needed)
d.bark(); // ✓ OK now
}
}
instanceof Operator
Check object type before downcasting.
class Animal { }
class Dog extends Animal { }
class Cat extends Animal { }
public class Main {
public static void main(String[] args) {
Animal a = new Dog();
if (a instanceof Dog) {
Dog d = (Dog) a;
System.out.println("It's a Dog");
}
if (a instanceof Cat) {
System.out.println("It's a Cat");
} else {
System.out.println("Not a Cat");
}
}
}
Real-World Example: Payment System
class Payment {
double amount;
Payment(double amount) {
this.amount = amount;
}
void processPayment() {
System.out.println("Processing payment: " + amount);
}
}
class CreditCardPayment extends Payment {
String cardNumber;
CreditCardPayment(double amount, String cardNumber) {
super(amount);
this.cardNumber = cardNumber;
}
@Override
void processPayment() {
System.out.println("Processing credit card payment: " + amount);
System.out.println("Card: " + cardNumber);
}
}
class UPIPayment extends Payment {
String upiId;
UPIPayment(double amount, String upiId) {
super(amount);
this.upiId = upiId;
}
@Override
void processPayment() {
System.out.println("Processing UPI payment: " + amount);
System.out.println("UPI ID: " + upiId);
}
}
class CashPayment extends Payment {
CashPayment(double amount) {
super(amount);
}
@Override
void processPayment() {
System.out.println("Processing cash payment: " + amount);
}
}
public class Main {
public static void main(String[] args) {
Payment[] payments = new Payment[3];
payments[0] = new CreditCardPayment(1000, "1234-5678-9012-3456");
payments[1] = new UPIPayment(500, "user@upi");
payments[2] = new CashPayment(200);
// Polymorphic processing
for (Payment payment : payments) {
payment.processPayment();
System.out.println();
}
}
}
Benefits of Polymorphism
- Flexibility: Same interface, different implementations
- Code Reusability: Write once, use with many types
- Maintainability: Easy to extend
- Loose Coupling: Reduce dependencies
- Extensibility: Add new types without changing existing code
Example: Employee System
class Employee {
String name;
double salary;
Employee(String name, double salary) {
this.name = name;
this.salary = salary;
}
double calculateBonus() {
return salary * 0.1; // 10% bonus
}
void displayInfo() {
System.out.println("Name: " + name);
System.out.println("Salary: " + salary);
System.out.println("Bonus: " + calculateBonus());
}
}
class Manager extends Employee {
Manager(String name, double salary) {
super(name, salary);
}
@Override
double calculateBonus() {
return salary * 0.2; // 20% bonus
}
}
class Developer extends Employee {
Developer(String name, double salary) {
super(name, salary);
}
@Override
double calculateBonus() {
return salary * 0.15; // 15% bonus
}
}
public class Main {
public static void main(String[] args) {
Employee[] employees = new Employee[3];
employees[0] = new Employee("John", 30000);
employees[1] = new Manager("Alice", 50000);
employees[2] = new Developer("Bob", 40000);
for (Employee emp : employees) {
emp.displayInfo();
System.out.println();
}
}
}
Polymorphic Parameters
class Animal {
void sound() {
System.out.println("Animal sound");
}
}
class Dog extends Animal {
@Override
void sound() {
System.out.println("Bark");
}
}
class Cat extends Animal {
@Override
void sound() {
System.out.println("Meow");
}
}
class AnimalTest {
// Method accepts any Animal type
static void makeSound(Animal animal) {
animal.sound(); // Polymorphic call
}
}
public class Main {
public static void main(String[] args) {
AnimalTest.makeSound(new Dog()); // Bark
AnimalTest.makeSound(new Cat()); // Meow
AnimalTest.makeSound(new Animal()); // Animal sound
}
}
Rules for Polymorphism
- Inheritance required
- Method overriding required
- Parent reference, child object
- Method resolution at runtime
- Cannot access child-specific methods through parent reference
- instanceof to check type
- Downcasting needs explicit cast
Compile-time vs Runtime
| Feature | Compile-time | Runtime |
|---|---|---|
| Also called | Static | Dynamic |
| Achieved by | Overloading | Overriding |
| Binding | Early | Late |
| Decided | Compile time | Runtime |
| Performance | Faster | Slightly slower |
| Example | add(int, int) vs add(double, double) | Parent ref = new Child() |
Quick Reference
// Runtime Polymorphism
class Parent {
void method() {
System.out.println("Parent");
}
}
class Child extends Parent {
@Override
void method() {
System.out.println("Child");
}
}
// Usage
Parent p = new Child(); // Upcasting
p.method(); // Output: Child (runtime decision)
// Check type
if (p instanceof Child) {
Child c = (Child) p; // Downcasting
}
Common Mistakes
Mistake 1: Accessing Child Methods
class Parent { }
class Child extends Parent {
void childMethod() { }
}
Parent p = new Child();
// p.childMethod(); // ✗ Error: Parent reference
Child c = (Child) p;
c.childMethod(); // ✓ OK after downcasting
Mistake 2: Invalid Cast
Parent p = new Parent();
Child c = (Child) p; // ✗ Runtime error: ClassCastException
Exam Tips
Remember:
- Polymorphism = many forms
- Two types: Compile-time (overloading), Runtime (overriding)
- Runtime uses inheritance + overriding
- Parent reference, child object
- Method resolved at runtime
- Use instanceof before downcasting
- Upcasting implicit, downcasting explicit
- Enables flexible code
- Cannot access child-specific members
- Core OOP concept
Common Questions:
- What is polymorphism?
- Types of polymorphism?
- Compile-time vs runtime?
- What is upcasting?
- What is downcasting?
- What is dynamic method dispatch?
- Benefits of polymorphism?
- instanceof operator?