Java Polymorphism: Unleashing the Full Potential of Object-Oriented Programming
Polymorphism is the ability of objects of different classes to be used interchangeably. This is made possible by inheritance, where a subclass can be treated as its parent class. Polymorphism allows us to write code that works with objects of different classes, as long as they share a common ancestor class or interface.
In Java, polymorphism is most commonly achieved through method overriding. When a subclass overrides a method of its parent class, it can provide its own implementation of the method. When an object of the subclass is referred to using a reference of its parent class, the implementation of the method that is called is the one in the subclass.
For example, consider the following code:
public class Animal {
public void speak() {
System.out.println("I am an animal.");
}
}
public class Dog extends Animal {
@Override
public void speak() {
System.out.println("Woof!");
}
}
public class Cat extends Animal {
@Override
public void speak() {
System.out.println("Meow!");
}
}
public class Main {
public static void main(String[] args) {
Animal a1 = new Animal();
Animal a2 = new Dog();
Animal a3 = new Cat();
a1.speak(); // "I am an animal."
a2.speak(); // "Woof!"
a3.speak(); // "Meow!"
}
}
Here, we define an Animal
class with a speak
method that prints "I am an animal," and two subclasses, Dog
and Cat
, that override the speak
method with their own implementations. In the main
method, we create objects of each class and assign them to variables of type Animal
. When we call the speak
method on each object, Java determines which implementation of the method to call based on the actual type of the object (Animal
, Dog
, or Cat
), not the reference type (Animal
). This allows us to write code that works with objects of different classes, as long as they share a common ancestor class.
Another way to achieve polymorphism in Java is through interfaces. An interface is a collection of abstract methods that define a contract for classes that implement the interface. When a class implements an interface, it must provide implementations for all of the methods defined in the interface. This allows us to write code that works with objects of different classes, as long as they implement the same interface.
For example, consider the following code:
public interface Shape {
double getArea();
}
public class Rectangle implements Shape {
private double width;
private double height;
public Rectangle(double width, double height) {
this.width = width;
this.height = height;
}
@Override
public double getArea() {
return width * height;
}
}
public class Circle implements Shape {
private double radius;
public Circle(double radius) {
this.radius = radius;
}
@Override
public double getArea() {
return Math.PI * radius * radius;
}
}
public class Main {
public static void main(String[] args) {
Shape s1 = new Rectangle(3, 4);
Shape s2 = new Circle(2);
System.out.println(s1.getArea()); // 12.0
System.out.println(s2.getArea()); // 12.566370614359172
}
}
Here, we define an interface
Shape
with an abstract
method getArea
, which is implemented by two classes, Rectangle
and Circle
. In the main
method, we create objects of Rectangle
and Circle
and assign them to variables of type Shape
. When we call the getArea
method on each object, Java determines which implementation of the method to call based on the actual type of the object (Rectangle
or Circle
), not the reference type (Shape
). This allows us to write code that works with objects of different classes, as long as they implement the same interface.
Polymorphism is a powerful feature of object-oriented programming that allows us to write flexible and reusable code. By designing classes and interfaces that share common behavior, we can write code that works with objects of different classes and interfaces, without having to write separate code for each class or interface.
Here’s a Java code example that demonstrates polymorphism using the Shape
interface and the Rectangle
and Circle
classes:
public interface Shape {
public double getArea();
}
public class Rectangle implements Shape {
private double width;
private double height;
public Rectangle(double width, double height) {
this.width = width;
this.height = height;
}
@Override
public double getArea() {
return width * height;
}
}
public class Circle implements Shape {
private double radius;
public Circle(double radius) {
this.radius = radius;
}
@Override
public double getArea() {
return Math.PI * radius * radius;
}
}
public class Main {
public static void main(String[] args) {
Shape rect = new Rectangle(5, 10);
Shape circle = new Circle(5);
System.out.println(rect.getArea()); // Output: 50.0
System.out.println(circle.getArea()); // Output: 78.53981633974483
}
}
In this example, we define the Shape
interface with a single method, getArea()
. We then implement this interface in the Rectangle
and Circle
classes, providing their own implementation of the getArea()
method. Finally, in the Main
class, we create objects of the Rectangle
and Circle
classes and assign them to variables of type Shape
. We can then call the getArea()
method on these objects, and Java will automatically determine which implementation of the method to call based on the actual type of the object (Rectangle
or Circle
). This demonstrates the power of polymorphism in allowing us to write flexible and reusable code.