
Java Polymorphism in Java
Polymorphism is one of the core concepts of Object-Oriented Programming (OOP) in Java. It allows one object to take many forms. In simple terms, polymorphism enables a single interface to represent different underlying forms (data types). It is achieved through method overriding and method overloading.
Types of Polymorphism in Java
Compile-time Polymorphism (Static Polymorphism): This type of polymorphism is resolved at compile time. It is typically achieved through method overloading and operator overloading (although operator overloading is not supported in Java).
Run-time Polymorphism (Dynamic Polymorphism): This type of polymorphism is resolved at runtime and is typically achieved through method overriding.
1. Compile-Time Polymorphism (Method Overloading)
Method overloading occurs when multiple methods in the same class have the same name but differ in the number or type of their parameters.
Example: Method Overloading
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 double values public double add(double a, double b) { return a + b; }}public class Main { public static void main(String[] args) { Calculator calc = new Calculator(); // Calls the method that adds two integers System.out.println("Sum of 2 integers: " + calc.add(5, 10)); // Calls the method that adds three integers System.out.println("Sum of 3 integers: " + calc.add(5, 10, 15)); // Calls the method that adds two doubles System.out.println("Sum of 2 doubles: " + calc.add(5.5, 10.5)); }}
Output:
Sum of 2 integers: 15Sum of 3 integers: 30Sum of 2 doubles: 16.0
Explanation: In this example, the method
add
is overloaded with different parameter types and counts. Java decides which method to invoke at compile time based on the arguments passed to it.
2. Run-Time Polymorphism (Method Overriding)
Method overriding occurs when a subclass provides a specific implementation for a method that is already defined in its superclass. This allows the subclass to alter the behavior of the inherited method.
Method overriding happens when a subclass has the same method signature (name and parameters) as a method in the parent class.
The version of the method that gets called is determined at runtime, based on the object being referenced.
Example: Method Overriding
class Animal { // Method in parent class public void sound() { System.out.println("Animal makes a sound"); }}class Dog extends Animal { // Method overriding in subclass @Override public void sound() { System.out.println("Dog barks"); }}class Cat extends Animal { // Method overriding in subclass @Override public void sound() { System.out.println("Cat meows"); }}public class Main { public static void main(String[] args) { Animal animal1 = new Dog(); Animal animal2 = new Cat(); // Runtime Polymorphism: The appropriate method is called based on the object animal1.sound(); // Calls Dog's sound() method animal2.sound(); // Calls Cat's sound() method }}
Output:
Dog barksCat meows
Explanation: In this example, both
Dog
andCat
override thesound
method of theAnimal
class. The runtime polymorphism occurs when we referenceDog
andCat
objects with theAnimal
type, and the correspondingsound
method is invoked at runtime based on the actual object type (Dog
orCat
).
Key Concepts of Polymorphism
Dynamic Method Dispatch: This is the mechanism that Java uses to implement runtime polymorphism. It decides which method to call based on the object being referred to at runtime.
Overloading vs. Overriding:
Overloading is compile-time polymorphism, achieved by creating multiple methods with the same name but different parameters.
Overriding is runtime polymorphism, achieved by redefining a method in the subclass that is already defined in the superclass.
Upcasting and Downcasting:
Upcasting is when a subclass object is referenced by a superclass reference variable. This is used for runtime polymorphism.
Downcasting is when you cast a superclass reference back to the subclass, which should be done carefully to avoid
ClassCastException
.
Why is Polymorphism Useful?
Code Reusability: With polymorphism, the same method can be reused with different types of data. This makes the code more flexible and reusable.
Decoupling: Polymorphism reduces tight coupling between classes, as it allows the user to interact with objects of different classes in a unified manner (using the superclass type).
Extensibility: New classes can be added to a system without modifying the existing code. If a new class extends an existing class and overrides its methods, the program can work with the new class seamlessly.
Real-world Example of Polymorphism
Think of a payment system where you have different payment methods like CreditCard, PayPal, and Bitcoin. Each payment method processes payments differently, but they all have a common method processPayment()
.
class PaymentMethod { public void processPayment() { System.out.println("Processing payment..."); }}class CreditCard extends PaymentMethod { @Override public void processPayment() { System.out.println("Processing payment via Credit Card..."); }}class PayPal extends PaymentMethod { @Override public void processPayment() { System.out.println("Processing payment via PayPal..."); }}class Bitcoin extends PaymentMethod { @Override public void processPayment() { System.out.println("Processing payment via Bitcoin..."); }}public class Main { public static void main(String[] args) { PaymentMethod payment1 = new CreditCard(); PaymentMethod payment2 = new PayPal(); PaymentMethod payment3 = new Bitcoin(); // All payments are processed, but each one uses a different method payment1.processPayment(); // Credit Card payment payment2.processPayment(); // PayPal payment payment3.processPayment(); // Bitcoin payment }}
Output:
Processing payment via Credit Card...Processing payment via PayPal...Processing payment via Bitcoin...
Explanation: Here,
processPayment()
is overridden in each subclass to provide specific behavior for each payment method, and polymorphism allows us to call the same method,processPayment()
, for all payment types, regardless of the actual object.
Summary
Polymorphism allows objects of different classes to be treated as objects of a common superclass.
Method Overloading (compile-time) and Method Overriding (runtime) are two primary ways to achieve polymorphism in Java.
Runtime polymorphism is achieved through method overriding and allows the correct method to be invoked based on the actual object type at runtime.
Let me know if you'd like more examples or clarification on any aspect! ?