Home » Polymorphism in Java

Polymorphism in Java

Polymorphism in Java

Polymorphism in Java is one of the main aspects of Object-Oriented Programming(OOP). The word polymorphism can be broken down into Poly and morphs, as “Poly” means many and “Morphs” means forms. In simple words, we can say that the ability of a message to be represented in many forms.

Polymorphism is considered one of the important features of Object-Oriented Programming. Polymorphism allows us to perform a single action in different ways. In other words, polymorphism allows you to define one interface and have multiple implementations. The word “poly” means many and “morphs” means forms, So it means many forms.

Real-Life Examples of Polymorphism

An individual can have different relationships with different people. A woman can be a mother, a daughter, a sister, and a friend, all at the same time, i.e. she performs other behaviors in different situations.

The human body has different organs. Every organ has a different function to perform; the heart is responsible for blood flow, the lungs for breathing, the brain for cognitive activity, and the kidneys for excretion. So we have a standard method function that performs differently depending upon the organ of the body.

Types of Java Polymorphism

In Java Polymorphism is mainly divided into two types:

  • Compile-time Polymorphism
  • Runtime Polymorphism
java-polymorphism.png

Compile-Time Polymorphism in Java

It is also known as static polymorphism. This type of polymorphism is achieved by function overloading.

Method Overloading

Method overloading in Java refers to the ability to define multiple methods in the same class with the same name but with different parameters. Java compiler differentiates these methods based on the number of parameters or the data types of parameters.

Example

public class Calculator {

    // Method to add two integers
    public int add(int a, int b) {
        return a + b;
    }

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

    // Method to add two doubles
    public double add(double a, double b) {
        return a + b;
    }

    // Method to concatenate two strings
    public String add(String a, String b) {
        return a + b;
    }

    public static void main(String[] args) {
        Calculator calculator = new Calculator();

        // Calling the overloaded methods
        System.out.println("Sum of 5 and 10 is: " + calculator.add(5, 10));
        System.out.println("Sum of 5, 10, and 15 is: " + calculator.add(5, 10, 15));
        System.out.println("Sum of 3.5 and 2.7 is: " + calculator.add(3.5, 2.7));
        System.out.println("Concatenation of 'Hello' and 'World' is: " + calculator.add("Hello", "World"));
    }
}
Java

Output

Sum of 5 and 10 is: 15
Sum of 5, 10, and 15 is: 30
Sum of 3.5 and 2.7 is: 6.2
Concatenation of 'Hello' and 'World' is: HelloWorld
Java

Runtime Polymorphism in Java

It is also known as Dynamic Method Dispatch. It is a process in which a function call to the overridden method is resolved at Runtime. This type of polymorphism is achieved by Method Overriding.

Method Overriding

Method overriding in Java occurs when a subclass provides a specific implementation of a method that is already defined in its superclass. The method in the subclass should have the same name, return type, and parameter list as the method in the superclass. Method overriding is used to provide specific behavior for a method in a subclass.

Example

class Animal {
    public void makeSound() {
        System.out.println("Animal makes a sound");
    }
}

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

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

public class Main {
    public static void main(String[] args) {
        Animal animal1 = new Animal();
        Animal animal2 = new Dog();
        Animal animal3 = new Cat();

        animal1.makeSound(); // Output: Animal makes a sound
        animal2.makeSound(); // Output: Dog barks
        animal3.makeSound(); // Output: Cat meows
    }
}
Java

Output

Animal makes a sound
Dog barks
Cat meows
Java

Advantages of Polymorphism in Java

  • Code Reusability: Polymorphism allows methods to be reused across different classes, reducing the need for redundant code.
  • Flexibility and Extensibility: Polymorphism enables the development of flexible and extensible code. New classes can be added without modifying existing code, as long as they adhere to the required interfaces or inherit from existing classes.
  • Simplification of Code: Polymorphism simplifies code by allowing methods to be defined in superclasses and overridden in subclasses. This promotes a clean and organized class hierarchy.
  • Enhanced Maintainability: Since polymorphism encourages code reuse and modularization, it enhances code maintainability. Changes made in one part of the codebase may have minimal impact on other parts, making it easier to manage and debug.
  • Dynamic Method Dispatch: Polymorphism enables dynamic method dispatch, where the appropriate method implementation is determined at runtime based on the actual object type. This promotes flexibility and runtime polymorphism.
  • Support for Interfaces: Polymorphism facilitates the use of interfaces, allowing objects of different classes to be treated interchangeably based on shared behavior defined by interfaces. This promotes loose coupling and abstraction.
  • Facilitation of Overloading and Overriding: Polymorphism supports method overloading and overriding, allowing for the creation of multiple methods with the same name but different behaviors. This promotes code clarity and flexibility.
  • Encouragement of Design Patterns: Polymorphism is central to many design patterns in Java, such as the Strategy pattern, Factory pattern, and Decorator pattern. Leveraging polymorphism in design patterns leads to more maintainable and scalable software architectures.

Disadvantages of Polymorphism in Java

  • Runtime Overhead: Dynamic method dispatch, which is central to polymorphism, can incur runtime overhead due to the additional lookup required to determine the appropriate method implementation. This overhead may be negligible for small programs but can become significant in performance-critical applications.
  • Complexity: Polymorphism can introduce complexity, especially in large codebases or when used improperly. Understanding which method implementation will be invoked at runtime may require tracing through multiple classes and hierarchies, leading to code that is harder to comprehend and maintain.
  • Hidden Behavior: Polymorphism can lead to hidden behavior, where the actual method implementation called may not be immediately evident from the code. This can make debugging and troubleshooting more challenging, particularly for developers unfamiliar with the codebase.
  • Performance Impact: While polymorphism promotes flexibility and code reuse, it can sometimes lead to performance degradation, especially when method invocations involve numerous virtual method calls or when complex inheritance hierarchies are present. This performance impact may necessitate optimization efforts.
  • Potential for Inefficiency: In certain scenarios, polymorphism may result in inefficient code. For example, method invocations through polymorphic references may incur additional memory overhead compared to direct method calls, as they may involve extra indirection through method tables or vtables.
  • Compiler Optimizations Limitation: Compiler optimizations for polymorphic code can be limited, particularly in the presence of dynamic class loading, reflection, or complex inheritance structures. This may restrict the extent to which the compiler can optimize method dispatch and may impact runtime performance.
  • Difficulty in Understanding Control Flow: Polymorphism can make it harder to understand the control flow of a program, especially when multiple classes override the same method. Determining which method implementation will be executed at runtime requires careful analysis of the inheritance hierarchy and object types involved.
  • Potential for Inheritance Issues: Inheritance hierarchies used in polymorphic code can sometimes lead to design issues such as the fragile base class problem or the diamond problem. These issues can complicate code maintenance and evolution, particularly in large and evolving codebases.

Difference between Compile-time and Run-time Polymorphism in Java

Compile Time PolymorphismRun time Polymorphism
In run-time Polymorphism, the call is not resolved by the compiler.It is also known as Static binding, Early binding, and overloading as well.
It is also known as Dynamic binding, Late binding, and overriding as well.Method overriding is the runtime polymorphism having the same method with the same parameters or signature but associated with compared, different classes.
It provides slow execution as compared to early binding because the method that needs to be executed is known at the runtime.Method overloading is the compile-time polymorphism where more than one methods share the same name with different parameters or signatures and different return types.
It is achieved by function overloading and operator overloading.It is achieved by virtual functions and pointers.
It provides fast execution because the method that needs to be executed is known early at the compile time.It provides slow execution as compare to early binding because the method that needs to be executed is known at the runtime.
Compile time polymorphism is less flexible as all things execute at compile time.Run time polymorphism is more flexible as all things execute at run time.
Inheritance is not involved.Inheritance is involved.

Conclusion

polymorphism in Java is a powerful feature that promotes code reuse, flexibility, and extensibility. By allowing objects of different classes to be treated interchangeably based on shared behavior, polymorphism facilitates the creation of modular, maintainable, and scalable software systems. Through concepts such as method overloading, method overriding, and dynamic method dispatch, polymorphism enables developers to write clean, concise code and leverage abstraction to manage complexity effectively.

Frequently Asked Questions

What is polymorphism in Java?

Polymorphism in Java refers to the ability of objects of different classes to be treated as objects of a common superclass. It allows methods to be invoked on objects without knowing their specific types at compile time.

How is polymorphism achieved in Java?

Polymorphism in Java is primarily achieved through method overriding and method overloading. Method overriding allows a subclass to provide a specific implementation of a method defined in its superclass, while method overloading enables the creation of multiple methods with the same name but different parameter lists.

What is method overriding in Java?

Method overriding in Java occurs when a subclass provides a specific implementation of a method that is already defined in its superclass. The subclass method must have the same name, return type, and parameter list as the method in the superclass.

What is method overloading in Java?

Method overloading in Java refers to the ability to define multiple methods in the same class with the same name but different parameter lists. Java compiler differentiates these methods based on the number or types of parameters.

What is dynamic method dispatch in Java?

Dynamic method dispatch is a mechanism in Java where the appropriate method implementation is determined at runtime based on the actual type of the object being referenced. It allows for runtime polymorphism, enabling the invocation of overridden methods from subclass objects using superclass references.