Polymorphism

Introduction

Polymorphism means “many forms”. One interface can have multiple implementations.

Greek: poly = many, morph = forms


Types of Polymorphism

  1. Compile-time Polymorphism (Static)

    • Method Overloading
    • Operator Overloading (not in Java)
  2. 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

  1. Flexibility: Same interface, different implementations
  2. Code Reusability: Write once, use with many types
  3. Maintainability: Easy to extend
  4. Loose Coupling: Reduce dependencies
  5. 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

  1. Inheritance required
  2. Method overriding required
  3. Parent reference, child object
  4. Method resolution at runtime
  5. Cannot access child-specific methods through parent reference
  6. instanceof to check type
  7. Downcasting needs explicit cast

Compile-time vs Runtime

FeatureCompile-timeRuntime
Also calledStaticDynamic
Achieved byOverloadingOverriding
BindingEarlyLate
DecidedCompile timeRuntime
PerformanceFasterSlightly slower
Exampleadd(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:

  1. Polymorphism = many forms
  2. Two types: Compile-time (overloading), Runtime (overriding)
  3. Runtime uses inheritance + overriding
  4. Parent reference, child object
  5. Method resolved at runtime
  6. Use instanceof before downcasting
  7. Upcasting implicit, downcasting explicit
  8. Enables flexible code
  9. Cannot access child-specific members
  10. 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?