Introduction
Polymorphism is one of the core concepts of Object-Oriented Programming (OOP) that plays a pivotal role in making Java a powerful and flexible language. It allows objects to be treated as instances of their parent class rather than their actual class. The word "polymorphism" means "many shapes" and it enables a single interface to be used for a general class of actions.
What is Polymorphism?
Polymorphism in Java allows one interface to be used for different data types. It can be divided into two types:
Compile-time Polymorphism (Method Overloading)
Run-time Polymorphism (Method Overriding)
Compile-time Polymorphism (Method Overloading)
Method Overloading occurs when multiple methods in the same class have the same name but different parameters. The method to be called is determined at compile-time based on the method signature.
Run-time Polymorphism (Method Overriding)
Method Overriding occurs when a subclass provides a specific implementation of a method that is already defined in its superclass. The method call is determined at runtime based on the object type.
Fundamentals of Polymorphism
Polymorphism is based on the inheritance hierarchy. When a method in a subclass overrides a method in its superclass, the subclass version of the method is executed.
Benefits of Polymorphism
Code Reusability: Polymorphism promotes the reusability of code.
Maintainability: It makes the code easier to maintain.
Flexibility: It allows for flexible and extendable code.
Extensibility: New functionality can be easily added by creating new subclasses.
Inheritance and Variable Scope
Inheritance is the mechanism by which one class inherits the fields and methods of another class. In Java, a class can inherit from only one superclass but can implement multiple interfaces.
Super Keyword: Used to refer to the immediate parent class object.
This Keyword: Refers to the current class instance.
Scope of Variables: Class variables (static) and instance variables have different scopes. Local variables inside methods are not accessible outside their scope.
Method Overloading vs Method Overriding
Method Overloading
Same method name but different parameters.
Resolved at compile-time.
It can occur within the same class.
Method Overriding
Same method name, the same parameters.
Resolved at runtime.
Occurs between superclass and subclass.
Practical Examples
Simple Examples
Example 1: Compile-time Polymorphism (Overloading)
class MathOperations {
int add(int a, int b) {
return a + b;
}
double add(double a, double b) {
return a + b;
}
int add(int a, int b, int c) {
return a + b + c;
}
}
public class TestOverloading {
public static void main(String[] args) {
MathOperations math = new MathOperations();
System.out.println(math.add(5, 10)); // Output: 15
System.out.println(math.add(5.5, 2.2)); // Output: 7.7
System.out.println(math.add(1, 2, 3)); // Output: 6
}
}
Example 2: Run-time Polymorphism (Overriding)
class Animal {
void sound() {
System.out.println("Animal makes a sound");
}
}
class Dog extends Animal {
@Override
void sound() {
System.out.println("Dog barks");
}
}
public class TestOverriding {
public static void main(String[] args) {
Animal myDog = new Dog();
myDog.sound(); // Output: Dog barks
}
}
Example 3: Interface Polymorphism
interface Shape {
void draw();
}
class Circle implements Shape {
@Override
public void draw() {
System.out.println("Drawing Circle");
}
}
class Rectangle implements Shape {
@Override
public void draw() {
System.out.println("Drawing Rectangle");
}
}
public class TestInterfacePolymorphism {
public static void main(String[] args) {
Shape myShape = new Circle();
myShape.draw(); // Output: Drawing Circle
myShape = new Rectangle();
myShape.draw(); // Output: Drawing Rectangle
}
}
Complex Examples
Example 1: Polymorphism in Collections
import java.util.ArrayList;
import java.util.List;
abstract class Employee {
abstract void work();
}
class Developer extends Employee {
@Override
void work() {
System.out.println("Developer is coding");
}
}
class Designer extends Employee {
@Override
void work() {
System.out.println("Designer is designing");
}
}
public class TestPolymorphismInCollections {
public static void main(String[] args) {
List<Employee> employees = new ArrayList<>();
employees.add(new Developer());
employees.add(new Designer());
for (Employee emp : employees) {
emp.work();
}
}
}
Example 2: Polymorphism in a Payment System
abstract class Payment {
abstract void pay();
}
class CreditCardPayment extends Payment {
@Override
void pay() {
System.out.println("Payment made by credit card");
}
}
class PayPalPayment extends Payment {
@Override
void pay() {
System.out.println("Payment made by PayPal");
}
}
public class TestPaymentSystem {
public static void main(String[] args) {
Payment payment = new CreditCardPayment();
payment.pay(); // Output: Payment made by credit card
payment = new PayPalPayment();
payment.pay(); // Output: Payment made by PayPal
}
}
Example 3: Polymorphism in a Notification System
abstract class Notification {
abstract void notifyUser();
}
class EmailNotification extends Notification {
@Override
void notifyUser() {
System.out.println("Notifying user via Email");
}
}
class SMSNotification extends Notification {
@Override
void notifyUser() {
System.out.println("Notifying user via SMS");
}
}
public class TestNotificationSystem {
public static void main(String[] args) {
Notification notification = new EmailNotification();
notification.notifyUser(); // Output: Notifying user via Email
notification = new SMSNotification();
notification.notifyUser(); // Output: Notifying user via SMS
}
}
Examples without Polymorphism vs. with Polymorphism
Without Polymorphism
class Car {
void start() {
System.out.println("Car starts");
}
}
class Bike {
void start() {
System.out.println("Bike starts");
}
}
public class TestWithoutPolymorphism {
public static void main(String[] args) {
Car car = new Car();
car.start(); // Output: Car starts
Bike bike = new Bike();
bike.start(); // Output: Bike starts
}
}
With Polymorphism
abstract class Vehicle {
abstract void start();
}
class Car extends Vehicle {
@Override
void start() {
System.out.println("Car starts");
}
}
class Bike extends Vehicle {
@Override
void start() {
System.out.println("Bike starts");
}
}
public class TestWithPolymorphism {
public static void main(String[] args) {
Vehicle vehicle = new Car();
vehicle.start(); // Output: Car starts
vehicle = new Bike();
vehicle.start(); // Output: Bike starts
}
}
Real-world Use Case: Polymorphism in a Graphic Editor
Imagine a graphic editor that can handle various shapes like circles, rectangles, and triangles. Using polymorphism, you can define a common interface for all shapes and create specific implementations for each shape.
Shape Interface and Implementations
interface Shape {
void draw();
}
class Circle implements Shape {
@Override
public void draw() {
System.out.println("Drawing Circle");
}
}
class Rectangle implements Shape {
@Override
public void draw() {
System.out.println("Drawing Rectangle");
}
}
class Triangle implements Shape {
@Override
public void draw() {
System.out.println("Drawing Triangle");
}
}
Graphic Editor
import java.util.ArrayList;
import java.util.List;
public class GraphicEditor {
private List<Shape> shapes = new ArrayList<>();
public void addShape(Shape shape) {
shapes.add(shape);
}
public void drawShapes() {
for (Shape shape : shapes) {
shape.draw();
}
}
public static void main(String[] args) {
GraphicEditor editor = new GraphicEditor();
editor.addShape(new Circle());
editor.addShape(new Rectangle());
editor.addShape(new Triangle());
editor.drawShapes(); // Output: Drawing Circle, Drawing Rectangle, Drawing Triangle
}
}
Conclusion
Polymorphism is a fundamental concept in Java and OOP that allows objects to be treated as instances of their parent class rather than their actual class. This promotes code reusability, flexibility, and maintainability. Developers can create more robust and scalable applications by understanding and utilizing polymorphism.