Polymorphism is one of the core concepts in object-oriented programming (OOP), and Java fully supports it. The term polymorphism comes from the Greek words “poly” (meaning many) and “morph” (meaning form). In Java, polymorphism allows objects to be treated as instances of their parent class, even though they may be instances of derived classes. This allows one interface to be used for a general class of actions, making your code more flexible and extensible.
In this guide, we will explore the concept of polymorphism in Java, its types, benefits, and examples.
Polymorphism allows a single action to behave differently based on the object it is acting upon. In Java, polymorphism means that the same method or operator can perform different tasks. It enables objects of different types to be treated as objects of a common supertype.
In Java, polymorphism can be divided into two major types:
Compile-time polymorphism, also known as method overloading, occurs when multiple methods have the same name but differ in the number or type of parameters. The correct method is determined at compile time based on the method signature.
class Calculator {
// Method to add two integers
int add(int a, int b) {
return a + b;
}
// Overloaded method to add three integers
int add(int a, int b, int c) {
return a + b + c;
}
// Overloaded method to 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("Sum of 2 integers: " + calc.add(5, 10)); // Output: 15
System.out.println("Sum of 3 integers: " + calc.add(5, 10, 15)); // Output: 30
System.out.println("Sum of 2 doubles: " + calc.add(5.5, 10.5)); // Output: 16.0
}
}
In this example, the add
method is overloaded with different parameter types and counts. The appropriate method is called based on the parameters passed.
Runtime polymorphism, also known as method overriding, occurs when a subclass provides its own implementation for a method that is already defined in its superclass. The method that gets called is determined at runtime, depending on the object being referenced.
class Animal {
// Method in superclass
void sound() {
System.out.println("Animal makes a sound");
}
}
class Dog extends Animal {
// Overriding method in subclass
@Override
void sound() {
System.out.println("Dog barks");
}
}
class Main {
public static void main(String[] args) {
Animal animal = new Animal(); // Creating an Animal object
animal.sound(); // Output: Animal makes a sound
Animal dog = new Dog(); // Creating a Dog object using Animal reference
dog.sound(); // Output: Dog barks (Method overriding in action)
}
}
In this example, the sound()
method is defined in both the Animal
class and overridden in the Dog
class. The correct method is chosen at runtime based on the object type (whether animal
is an Animal
or Dog
).
In Java, dynamic method dispatch is the mechanism that determines which method to invoke based on the actual object at runtime, not the reference type. This is primarily related to method overriding, where the method to call is determined by the object that is assigned to the reference.
In the Main
class, the variable dog
is of type Animal
, but it holds an instance of Dog
. At runtime, the overridden method in the Dog
class is invoked, demonstrating polymorphism.
Method overloading is a common example of compile-time polymorphism. It occurs when there are multiple methods with the same name but different signatures. The method that will be called is resolved during the compilation based on the parameters passed.
class Printer {
void print(String message) {
System.out.println("String message: " + message);
}
void print(int number) {
System.out.println("Integer message: " + number);
}
}
public class Main {
public static void main(String[] args) {
Printer printer = new Printer();
printer.print("Hello, World!"); // Output: String message: Hello, World!
printer.print(123); // Output: Integer message: 123
}
}
In this example, the print
method is overloaded with different parameter types (String and int). The correct method is selected at compile-time based on the argument passed.
Method overriding is a key aspect of runtime polymorphism. It occurs when a subclass provides a specific implementation for a method that is already defined in its superclass. The overridden method is called at runtime based on the object type.
class Vehicle {
void drive() {
System.out.println("Vehicle is driving");
}
}
class Car extends Vehicle {
@Override
void drive() {
System.out.println("Car is driving");
}
}
class Main {
public static void main(String[] args) {
Vehicle vehicle = new Vehicle();
vehicle.drive(); // Output: Vehicle is driving
Vehicle car = new Car();
car.drive(); // Output: Car is driving (Method overriding in action)
}
}
In this case, the drive()
method is overridden in the Car
class. At runtime, the correct method is called depending on the object type, demonstrating polymorphism.