Polymorphism in Java

Polymorphism in Java

On Day 10, we will explore the concept of Polymorphism, one of the four pillars of Object-Oriented Programming (OOPs). Polymorphism enables Java objects to take multiple forms, allowing for flexible and dynamic code. It plays a crucial role in improving code maintainability and reusability by providing a way to perform a single action in different ways.


1. What is Polymorphism?

Polymorphism in Java allows one interface to be used for a general class of actions. The specific action is determined by the exact nature of the situation. The term "polymorphism" is derived from two Greek words:

  • Poly: Many

  • Morph: Forms

In Java, polymorphism manifests primarily in two forms:

  1. Compile-time (Static) Polymorphism – Achieved through method overloading.

  2. Run-time (Dynamic) Polymorphism – Achieved through method overriding.


2. Types of Polymorphism

A. Compile-time Polymorphism (Method Overloading)

Compile-time polymorphism occurs when multiple methods share the same name but differ in:

  • The number of parameters.

  • The type of parameters.

This is also known as method overloading. The method to be called is determined at compile-time based on the method signature.

Method Overloading Example:
class Calculator {
    // Method to add two integers
    public int add(int a, int b) {
        return a + b;
    }

    // Overloaded method to add three integers
    public int add(int a, int b, int c) {
        return a + b + c;
    }

    // Overloaded method to add two doubles
    public 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(2, 3));       // Calls add(int, int)
        System.out.println(calc.add(2, 3, 4));    // Calls add(int, int, int)
        System.out.println(calc.add(2.5, 3.5));   // Calls add(double, double)
    }
}

Explanation:

  • In the example above, the add() method is overloaded three times with different parameter lists.

  • Depending on the number and type of arguments passed, the appropriate add() method is invoked at compile-time.


B. Run-time Polymorphism (Method Overriding)

Run-time polymorphism occurs when a method is overridden in a subclass, and the method that gets executed is determined at run-time based on the object type (not the reference type). This is also known as method overriding.

To achieve run-time polymorphism, the following conditions must be met:

  1. The method in the subclass must have the same name, return type, and parameters as the method in the superclass.

  2. There must be inheritance (i.e., the subclass should inherit the superclass).

  3. Method overriding enables dynamic method dispatch where the method called depends on the object type, not the reference type.

Method Overriding Example:
class Animal {
    // Overridden method
    public void sound() {
        System.out.println("This animal makes a sound.");
    }
}

class Dog extends Animal {
    // Overriding the sound method
    @Override
    public void sound() {
        System.out.println("The dog barks.");
    }
}

class Cat extends Animal {
    // Overriding the sound method
    @Override
    public void sound() {
        System.out.println("The cat meows.");
    }
}

public class Main {
    public static void main(String[] args) {
        Animal myAnimal = new Animal();  // Animal object
        Animal myDog = new Dog();        // Dog object
        Animal myCat = new Cat();        // Cat object

        myAnimal.sound();   // Calls Animal's sound method
        myDog.sound();      // Calls Dog's sound method
        myCat.sound();      // Calls Cat's sound method
    }
}

Explanation:

  • In this example, the sound() method is overridden in both the Dog and Cat classes.

  • Although the reference type is Animal, the actual method that gets invoked at run-time is based on the object type (Dog or Cat).


3. Dynamic Method Dispatch

Dynamic Method Dispatch is the process by which a call to an overridden method is resolved at run-time rather than compile-time. This is a key aspect of run-time polymorphism in Java.

  • Upcasting: In run-time polymorphism, a reference of the superclass can point to an object of the subclass. This is called upcasting.

  • The method that gets called is determined by the actual object (subclass), not the reference type (superclass).

Example of Dynamic Method Dispatch:
class Animal {
    public void sound() {
        System.out.println("This animal makes a sound.");
    }
}

class Dog extends Animal {
    @Override
    public void sound() {
        System.out.println("The dog barks.");
    }
}

class Cat extends Animal {
    @Override
    public void sound() {
        System.out.println("The cat meows.");
    }
}

public class Main {
    public static void main(String[] args) {
        Animal animal;  // Superclass reference

        animal = new Dog();  // Reference points to Dog object
        animal.sound();      // Calls Dog's sound method

        animal = new Cat();  // Reference points to Cat object
        animal.sound();      // Calls Cat's sound method
    }
}

Explanation:

  • In this case, the reference type is Animal, but at run-time, the method of the actual object (Dog or Cat) is invoked. This is dynamic method dispatch.

4. Polymorphism with Interfaces

Polymorphism is also achieved through interfaces in Java. An interface defines a contract, and multiple classes can implement this interface in their own way. This allows you to treat different classes in a uniform way, as long as they implement the same interface.

Example of Polymorphism with Interfaces:
interface Animal {
    void sound();
}

class Dog implements Animal {
    @Override
    public void sound() {
        System.out.println("The dog barks.");
    }
}

class Cat implements Animal {
    @Override
    public void sound() {
        System.out.println("The cat meows.");
    }
}

public class Main {
    public static void main(String[] args) {
        Animal animal = new Dog();  // Dog implements Animal
        animal.sound();             // Calls Dog's implementation

        animal = new Cat();         // Cat implements Animal
        animal.sound();             // Calls Cat's implementation
    }
}

Explanation:

  • The Dog and Cat classes implement the Animal interface, and the sound() method is overridden in both classes.

  • Polymorphism allows the same interface reference (Animal) to be used to point to different object types (Dog or Cat).


5. Advantages of Polymorphism

  • Flexibility: Code can work with objects of different classes, even if they behave differently, making the system more flexible.

  • Code Reusability: You can write more generic and reusable code, as the same code can work for objects of different types.

  • Maintainability: Adding new types that conform to the existing system does not require changes to the polymorphic code.

  • Extensibility: New classes can be added without modifying existing classes, making the system more extensible.


6. Key Points to Remember

  • Overloading (Compile-time polymorphism): Same method name with different signatures (number, type of parameters).

  • Overriding (Run-time polymorphism): Same method signature in superclass and subclass, where the subclass provides a specific implementation.

  • Dynamic Method Dispatch: At run-time, the method of the actual object type is invoked, not the reference type.

  • Polymorphism with Interfaces: Different classes can implement the same interface, allowing uniformity in code.


Summary

By the end of Day 10, we will have a deep understanding of:

  1. What polymorphism is and why it’s important in Java.

  2. How to achieve compile-time polymorphism using method overloading.

  3. How to implement run-time polymorphism using method overriding.

  4. The role of dynamic method dispatch in method resolution.

  5. How to use interfaces to implement polymorphism in a structured and flexible way.

Polymorphism is a powerful concept that allows Java programs to be more dynamic, flexible, and maintainable, making it an essential part of mastering Object-Oriented Programming. So Stay tuned!! for further topics.