Polymorphism in Java

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.

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:

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 typescount, 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?

  1. Code Flexibility: Write generic code that works with multiple object types.
  2. Maintainability: Isolate changes to specific classes.
  3. Reusability: Share method names across related classes.
  4. 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

  1. Use @Override: Avoid typos and ensure correct overriding.
  2. Favor Interfaces for Polymorphism: Decouple code from implementations.
  3. Avoid Overloading with Ambiguous Parameters: Prevent confusion in method calls.
  4. 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.

Sharing Is Caring:
Subscribe
Notify of
0 Comments
Most Voted
Newest Oldest
Inline Feedbacks
View all comments