Polymorphism in Java
Polymorphism is one of the core concepts of object-oriented programming (OOP) that allows objects to be treated as instances of their parent class rather than their actual class. The word “polymorphism” means “many shapes” or “many forms.” In Java, polymorphism allows methods to do different things based on the object it is acting upon, even though they share the same name.
Types of Polymorphism
1. Compile-Time Polymorphism (Method Overloading)
Compile-time polymorphism, also known as static polymorphism or method overloading, occurs when multiple methods have the same name but different parameter lists within the same class. The method to be invoked is determined at compile-time.
- Method Overloading
public class MathUtils {
// Method to add two integers
public int add(int a, int b) {
return a + b;
}
// Overloaded method to add three integers
public int add(int a, int b, int c) {
return a + b + c;
}
// Overloaded method to add two double values
public double add(double a, double b) {
return a + b;
}
public static void main(String[] args) {
MathUtils math = new MathUtils();
System.out.println(math.add(2, 3)); // Calls add(int, int)
System.out.println(math.add(2, 3, 4)); // Calls add(int, int, int)
System.out.println(math.add(2.5, 3.5)); // Calls add(double, double)
}
}
2. Runtime Polymorphism (Method Overriding)
Runtime polymorphism, also known as dynamic polymorphism or method overriding, occurs when a subclass provides a specific implementation of a method that is already defined in its superclass. The method to be invoked is determined at runtime based on the object.
- Method Overriding
class Animal {
// Superclass method
void sound() {
System.out.println("Animal makes a sound");
}
}
class Dog extends Animal {
// Overridden method
@Override
void sound() {
System.out.println("Dog barks");
}
}
class Cat extends Animal {
// Overridden method
@Override
void sound() {
System.out.println("Cat meows");
}
}
public class TestPolymorphism {
public static void main(String[] args) {
Animal myDog = new Dog();
Animal myCat = new Cat();
myDog.sound(); // Calls Dog's sound method
myCat.sound(); // Calls Cat's sound method
}
}
Upcasting and Downcasting
Upcasting
Upcasting is the process of treating a subclass object as an instance of its superclass. This is always safe and does not require explicit casting.
- Example:
Animal animal = new Dog(); // Upcasting
animal.sound(); // Calls the overridden sound method in Dog class
Downcasting
Downcasting is the process of treating a superclass object as an instance of its subclass. This requires explicit casting and can be unsafe if not handled properly.
- Example:
Animal animal = new Dog(); // Upcasting
Dog dog = (Dog) animal; // Downcasting
dog.sound(); // Calls the sound method in Dog class
Polymorphism with Interfaces
Polymorphism is also achieved through interfaces. An interface defines a contract, and any class that implements the interface agrees to implement its methods. This allows objects of different classes to be treated uniformly through the interface type.
- Example:
interface Animal {
void sound();
}
class Dog implements Animal {
@Override
public void sound() {
System.out.println("Dog barks");
}
}
class Cat implements Animal {
@Override
public void sound() {
System.out.println("Cat meows");
}
}
public class TestInterfacePolymorphism {
public static void main(String[] args) {
Animal myDog = new Dog();
Animal myCat = new Cat();
myDog.sound(); // Calls Dog's sound method
myCat.sound(); // Calls Cat's sound method
}
}
Benefits of Polymorphism
- Code Reusability: Polymorphism allows for the reuse of existing code by defining common interfaces or base classes.
- Flexibility and Maintainability: Code becomes more flexible and easier to maintain, as new classes can be introduced with minimal changes to existing code.
- Reduced Complexity: Polymorphism simplifies code by allowing one interface to be used for a general class of actions.
Example Scenario: Shape Drawing
Consider a scenario where you have different types of shapes (e.g., Circle, Rectangle) and you want to draw them. Polymorphism allows you to define a common interface for all shapes and use it to draw any shape without knowing its specific type at compile-time.
- Example:
interface Shape {
void draw();
}
class Circle implements Shape {
@Override
public void draw() {
System.out.println("Drawing a Circle");
}
}
class Rectangle implements Shape {
@Override
public void draw() {
System.out.println("Drawing a Rectangle");
}
}
public class TestShapePolymorphism {
public static void main(String[] args) {
Shape shape1 = new Circle();
Shape shape2 = new Rectangle();
shape1.draw(); // Calls Circle's draw method
shape2.draw(); // Calls Rectangle's draw method
}
}
By understanding and using polymorphism, you can write more flexible and maintainable code, promoting a cleaner design and enabling easier extension and modification of your applications.