Polymorphism in Java
Introduction
Polymorphism is one of the core concepts of object-oriented programming (OOP), alongside inheritance, encapsulation, and abstraction. The term “polymorphism” comes from the Greek words “poly” (many) and “morph” (form), meaning “many forms.” In Java, polymorphism allows objects to be treated as instances of their parent class rather than their actual class. This feature enables one interface to be used for a general class of actions, simplifying code and enhancing its flexibility and maintainability.
Types of Polymorphism in Java
There are two main types of polymorphism in Java:
- Compile-time polymorphism (Method Overloading)
- Runtime polymorphism (Method Overriding)
Compile-time Polymorphism (Method Overloading)
Compile-time polymorphism is achieved through method overloading. This occurs when multiple methods in the same class have the same name but different parameters (different type or number of parameters). The correct method is determined at compile-time based on the method signature.
Example of method overloading:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class MathOperations {
// Overloaded method for adding two integers
int add(int a, int b) {
return a + b;
}
// Overloaded method for adding three integers
int add(int a, int b, int c) {
return a + b + c;
}
// Overloaded method for adding two double values
double add(double a, double b) {
return a + b;
}
}
public class Main {
public static void main(String[] args) {
MathOperations math = new MathOperations();
System.out.println(math.add(5, 3)); // Output: 8
System.out.println(math.add(5, 3, 2)); // Output: 10
System.out.println(math.add(5.5, 3.3)); // Output: 8.8
}
}
In this example, the add
method is overloaded with different parameter lists, demonstrating compile-time polymorphism.
Runtime Polymorphism (Method Overriding)
Runtime polymorphism is achieved through method overriding. This occurs when a subclass provides a specific implementation for a method that is already defined in its superclass. The overridden method in the subclass should have the same name, return type, and parameters as the method in the superclass. The method to be called is determined at runtime, based on the object’s actual type, not the reference type.
Example of method overriding:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class Animal {
void makeSound() {
System.out.println("Some sound");
}
}
class Dog extends Animal {
@Override
void makeSound() {
System.out.println("Bark");
}
}
class Cat extends Animal {
@Override
void makeSound() {
System.out.println("Meow");
}
}
public class Main {
public static void main(String[] args) {
Animal myAnimal = new Animal();
Animal myDog = new Dog();
Animal myCat = new Cat();
myAnimal.makeSound(); // Output: Some sound
myDog.makeSound(); // Output: Bark
myCat.makeSound(); // Output: Meow
}
}
In this example, the makeSound
method is overridden in the Dog
and Cat
subclasses.
The actual method that gets called depends on the object’s runtime type.
Advantages of Polymorphism
- Code Reusability: Polymorphism promotes code reuse. Methods can be written that use superclass types as parameters, making them applicable to a wider range of objects.
- Maintainability: Code becomes easier to maintain and extend. New subclasses can be added with little or no modification to existing code.
- Flexibility: Polymorphism allows for flexible and scalable code. You can introduce new subclasses with specific behaviors without changing the code that uses the superclass.
- Reduced Complexity: Polymorphism can help reduce the complexity of code by allowing the same interface to be used for different underlying forms (data types).
Implementing Polymorphism
Using Interfaces
Interfaces in Java provide a way to achieve polymorphism. An interface defines a contract that implementing classes must follow. This allows different classes to be treated uniformly through the interface type.
Example using interfaces:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
interface Animal {
void makeSound();
}
class Dog implements Animal {
@Override
public void makeSound() {
System.out.println("Bark");
}
}
class Cat implements Animal {
@Override
public void makeSound() {
System.out.println("Meow");
}
}
public class Main {
public static void main(String[] args) {
Animal myDog = new Dog();
Animal myCat = new Cat();
myDog.makeSound(); // Output: Bark
myCat.makeSound(); // Output: Meow
}
}
In this example, the Animal
interface is implemented by the Dog
and Cat
classes.
The makeSound
method is called polymorphically.
Using Abstract Classes
Abstract classes can also be used to achieve polymorphism. An abstract class can define abstract methods that must be implemented by its subclasses.
Example using abstract classes:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
abstract class Animal {
abstract void makeSound();
}
class Dog extends Animal {
@Override
void makeSound() {
System.out.println("Bark");
}
}
class Cat extends Animal {
@Override
void makeSound() {
System.out.println("Meow");
}
}
public class Main {
public static void main(String[] args) {
Animal myDog = new Dog();
Animal myCat = new Cat();
myDog.makeSound(); // Output: Bark
myCat.makeSound(); // Output: Meow
}
}
In this example, the Animal
abstract class defines the makeSound
method,
which is implemented by the Dog
and Cat
subclasses.
Conclusion
Polymorphism is a powerful feature of Java that enhances flexibility, reusability, and maintainability of code. By understanding and effectively implementing polymorphism through method overloading, method overriding, interfaces, and abstract classes, you can create more robust and scalable Java applications. Polymorphism allows developers to write cleaner code that can easily adapt to new requirements, making it a fundamental aspect of object-oriented programming.