Polymorphism in Java

Polymorphism in Java is a core concept in object-oriented programming (OOP) that plays a vital role. This article will explain what polymorphism is, its different types, and how it is used in Java. We will also explore its benefits and practical applications to give you a thorough understanding of polymorphism.

Contents:

  1. What is Polymorphism?
  2. Types of Polymorphism
  3. Compile-time Polymorphism
  4. Runtime Polymorphism
  5. Advantages of Polymorphism in Java
  6. Disadvantages of Polymorphism in Java
  7. Practical Applications of Polymorphism
  8. Difference between Compile-time and Run-time Polymorphism
  9. FAQs on Polymorphism in Java

What is Polymorphism?

Polymorphism, derived from the Greek words “poly” (many) and “morphos” (form), refers to the ability of an object to take on multiple forms or shapes. In Java, this means that a single method can perform different actions depending on the type of object it is operating on.

Types of Polymorphism

Polymorphism in Java is mainly categorized into two types:

  • Compile-time Polymorphism
  • Runtime Polymorphism

advertisement
advertisement

Compile-time Polymorphism

Compile-time polymorphism, also known as static polymorphism, is achieved by method overloading. This type of polymorphism is resolved during compile time, meaning the method to be executed is determined at the time of compilation.

Implementing Polymorphism in Java using Method Overloading

Method overloading is a way to achieve compile-time polymorphism. It allows a class to have more than one method with the same name, but with different parameters (different type, number, or both).

Syntax:

class ClassName
{
    // Method with two parameters
    returnType methodName(parameter1Type param1, parameter2Type param2)
    {
        // method body
    }
 
    // Method with three parameters
    returnType methodName(parameter1Type param1, parameter2Type param2, parameter3Type param3)
    {
        // method body
    }
}

Example of Method Overloading:

class Printer 
{
    // Method to print an integer
    public void print(int num) 
    {
        System.out.println("Printing integer: " + num);
    }
 
    // Method to print a double
    public void print(double num)
    {
        System.out.println("Printing double: " + num);
    }
 
    // Method to print a string
    public void print(String str)
    {
        System.out.println("Printing string: " + str);
    }
}
 
public class Main
{
    public static void main(String[] args)
    {
        Printer printer = new Printer();
 
        printer.print(5);
        printer.print(5.5);
        printer.print("Hello"); 
    }
}

The code demonstrates method overloading. It has three print methods in the Printer class, each taking a different data type (int, double, string). The main method calls print with various arguments, and Java calls the appropriate overloaded method based on the argument type, achieving specialized printing for each data type.

The output for this code will be:

advertisement
Printing integer: 5
Printing double: 5.5
Printing string: Hello

Runtime Polymorphism

Runtime polymorphism, also known as dynamic polymorphism, is achieved through method overriding. Method overriding occurs when a subclass provides a specific implementation for a method that is already defined in its superclass.

Syntax:

class Superclass
{
    // Method to be overridden
    returnType methodName()
    {
        // method body
    }
}
 
class Subclass extends Superclass
{
    @Override
    returnType methodName()
    {
        // overridden method body
    }
}

Example of Method Overriding:

advertisement
// Superclass
class Employee
{
    public void work()
    {
        System.out.println("Employee is working");
    }
}
 
// Subclass
class Manager extends Employee
{
    @Override
    public void work()
    {
        System.out.println("Manager is managing projects");
    }
}
 
// Another Subclass
class Developer extends Employee
{
    @Override
    public void work()
    {
        System.out.println("Developer is writing code");
    }
}
 
public class Main
{
    public static void main(String[] args)
    {
        // Employee reference and object
        Employee emp1 = new Employee();
        // Employee reference but Manager object
        Employee emp2 = new Manager();
        // Employee reference but Developer object
        Employee emp3 = new Developer();
 
        emp1.work(); 
        emp2.work();  
        emp3.work(); 
    }
}

The code defines a base class Employee with a generic work() method. Manager and Developer inherit from Employee and override work() with specific behaviors.

The main method creates Employee references for Manager and Developer objects. When calling work() on these references, the actual implementation from the object’s type (Manager or Developer) is executed.

Output:

Employee is working
Manager is managing projects
Developer is writing code

This example explains how polymorphism allows us to treat different objects through a common interface (Employee) while maintaining their unique behaviors.

Advantages of Polymorphism in Java

  • Code Reusability: You can write a method once and use it for different types of actions, which saves time and effort.
  • Flexibility: It’s easy to add new classes without needing to change existing code.
  • Maintainability: When you make changes to the parent class, those changes automatically apply to the child classes, making updates simpler.
  • Readability: By hiding complex details, polymorphism makes your code easier to read and understand.

Disadvantages of Polymorphism in Java

  • Complexity: It can make your code harder to understand and follow, especially for new developers.
  • Performance Overhead: Deciding which method to call at runtime can slow down your program a bit.
  • Debugging Difficulty: It can be more challenging to debug because it’s not always clear which method is being executed.
  • Memory Usage: Using multiple objects and inheritance hierarchies can lead to higher memory consumption.
  • Potential for Misuse: If not used correctly, polymorphism can lead to confusing and hard-to-maintain code.

Practical Applications of Polymorphism

Polymorphism is widely used in various scenarios such as:

  • Design Patterns: Many design patterns like Factory, Strategy, and Command make extensive use of polymorphism to achieve flexibility and scalability.
  • Frameworks and Libraries: Java frameworks like Spring and Hibernate use polymorphism to provide flexible and extensible architectures.
  • Collections API: Java Collections Framework uses polymorphism extensively. For instance, you can use polymorphic algorithms that work on objects of the `Collection` interface, regardless of their specific implementations (like `ArrayList`, `LinkedList`, etc.).

Example 1: Notification System

Consider a notification system where different types of notifications (e.g., email, SMS, push) are sent to users. Each notification type can be treated as an instance of a common `Notification` class.

// Superclass
class Notification
{
    public void notifyUser()
    {
        System.out.println("Sending notification");
    }
}
 
// Subclass
class EmailNotification extends Notification
{
    @Override
    public void notifyUser()
    {
        System.out.println("Sending email notification");
    }
}
 
// Another Subclass
class SMSNotification extends Notification
{
    @Override
    public void notifyUser()
    {
        System.out.println("Sending SMS notification");
    }
}
 
// Another Subclass
class PushNotification extends Notification
{
    @Override
    public void notifyUser()
    {
        System.out.println("Sending push notification");
    }
}
 
public class Main
{
    public static void main(String[] args)
    {
        Notification email = new EmailNotification();
        Notification sms = new SMSNotification();
        Notification push = new PushNotification();
 
        email.notifyUser(); 
        sms.notifyUser(); 
        push.notifyUser(); 
    }
}

The provided Java code demonstrates polymorphism through method overriding. We have a superclass Notification and three subclasses EmailNotification, SMSNotification, and PushNotification. Each subclass overrides the notifyUser() method from the superclass to provide its specific implementation for sending a particular type of notification..

Output:

Sending email notification
Sending SMS notification
Sending push notification
  • email.notifyUser() calls the overridden notifyUser() method in EmailNotification, printing “Sending email notification”.
  • sms.notifyUser() calls the overridden notifyUser() method in SMSNotification, printing “Sending SMS notification”.
  • push.notifyUser() calls the overridden notifyUser() method in PushNotification, printing “Sending push notification”.

This demonstrates runtime polymorphism in Java. The specific notifyUser() method that gets called depends on the actual object’s type at runtime, even though the references (email, sms, and push) are all of type Notification. This allows for flexible handling of different notification types.

Example 2: Payment Processing

In a payment processing system, different types of payment methods (e.g., credit card, PayPal, bank transfer) can be handled through a common `Payment` class.

// Superclass
class Payment
{
    public void processPayment()
    {
        System.out.println("Processing payment");
    }
}
 
// Subclass
class CreditCardPayment extends Payment
{
    @Override
    public void processPayment()
    {
        System.out.println("Processing credit card payment");
    }
}
 
// Another Subclass
class PayPalPayment extends Payment
{
    @Override
    public void processPayment()
    {
        System.out.println("Processing PayPal payment");
    }
}
 
// Another Subclass
class BankTransferPayment extends Payment
{
    @Override
    public void processPayment()
    {
        System.out.println("Processing bank transfer payment");
    }
}
 
public class Main
{
    public static void main(String[] args)
    {
        Payment creditCard = new CreditCardPayment();
        Payment payPal = new PayPalPayment();
        Payment bankTransfer = new BankTransferPayment();
 
        creditCard.processPayment();  
        payPal.processPayment(); 
        bankTransfer.processPayment();
    }
}

This Java program demonstrates inheritance and method overriding. The superclass Payment has a method processPayment() that prints “Processing payment“. There are three subclasses: CreditCardPayment, PayPalPayment, and BankTransferPayment, each overriding processPayment() to print specific messages related to their payment types.

In the main method, instances of each subclass are created and referenced by the superclass Payment. This illustrates polymorphism, where a superclass reference can refer to subclass objects. When calling processPayment() on each instance, the overridden method corresponding to each subclass is executed.

The output of the program is:

Processing credit card payment
Processing PayPal payment
Processing bank transfer payment

This output demonstrates the dynamic dispatch of method calls based on the actual object type, showcasing the flexibility and reusability of inheritance and polymorphism in Java.

Difference between Compile-time and Run-time Polymorphism

Here’s a table summarizing the differences between compile-time and run-time polymorphism in Java:

Feature Compile-time Polymorphism (Method Overloading) Run-time Polymorphism (Method Overriding)
Type of Polymorphism Static Polymorphism Dynamic Polymorphism
Binding Time Early Binding (or Static Binding) Late Binding (or Dynamic Binding)
Also Known As Static Polymorphism, Early Binding, Method Overloading Dynamic Polymorphism, Late Binding, Method Overriding
Occurs in Single Class (or Subclass) Parent Class and its Subclasses
Decision Time At compile-time At run-time
Method Signature Must differ either by the number of parameters or type of parameters Must have the same method signature (name and parameters)
Implementation Overloading allows multiple methods with the same name in the same class or subclass, differentiated by method signature. Overriding redefines a method in a subclass that is already defined in its superclass, with the same method signature.
Inheritance Requirement Not required. Method overloading can occur in the same class or subclass without inheritance. Required. Method overriding occurs between a superclass and its subclass.
Performance Generally faster, as the binding is done at compile-time. Slightly slower, as the binding is done at runtime.
Flexibility Less flexible, as the method to be executed is determined at compile-time. More flexible, as the method to be executed can be determined at runtime based on the object.
Usage Useful when different behaviors are required based on the type and number of parameters. Useful when you want to implement specific behavior for an object based on its subclass type.

FAQs on Polymorphism in Java

1. What is polymorphism in Java?

Polymorphism in Java is the ability of a single interface to represent different underlying forms (data types).

2. What are the types of polymorphism in Java?

There are two types: compile-time polymorphism (method overloading) and runtime polymorphism (method overriding).

3. How does method overloading achieve polymorphism?

Method overloading allows a class to have multiple methods with the same name but different parameters, providing compile-time polymorphism.

4. How does method overriding achieve polymorphism?

Method overriding allows a subclass to provide a specific implementation of a method already defined in its superclass, providing runtime polymorphism.

5. What are the benefits of using polymorphism?

Benefits include code reusability, maintainability, extensibility, and flexibility. By leveraging polymorphism, Java developers can create more dynamic and adaptable applications, leading to robust and scalable software solutions.

Key Points to Remember

Here is the list of key points we need to remember about the “Polymorphism in Java”.

  • Polymorphism allows objects of different classes to be treated as objects of a common class.
  • There are two types of polymorphism: compile-time (method overloading) and runtime (method overriding).
  • Compile-time polymorphism is determined at compile time based on the method arguments.
  • Runtime polymorphism is determined at runtime based on the object’s type.
  • Polymorphism promotes code reusability, maintainability, extensibility, and flexibility.
  • Examples of polymorphism include design patterns, frameworks, and the Collections API.
  • Method overriding allows subclasses to provide specialized implementations of inherited methods.

If you find any mistake above, kindly email to [email protected]

advertisement
advertisement
Subscribe to our Newsletters (Subject-wise). Participate in the Sanfoundry Certification contest to get free Certificate of Merit. Join our social networks below and stay updated with latest contests, videos, internships and jobs!

Youtube | Telegram | LinkedIn | Instagram | Facebook | Twitter | Pinterest
Manish Bhojasia - Founder & CTO at Sanfoundry
Manish Bhojasia, a technology veteran with 20+ years @ Cisco & Wipro, is Founder and CTO at Sanfoundry. He lives in Bangalore, and focuses on development of Linux Kernel, SAN Technologies, Advanced C, Data Structures & Alogrithms. Stay connected with him at LinkedIn.

Subscribe to his free Masterclasses at Youtube & discussions at Telegram SanfoundryClasses.