Polymorphism in Java
Learn how polymorphism allows the same interface or method call to represent different behaviors in Java programs.
What is Polymorphism?
Polymorphism means "many forms". In Java, polymorphism allows one method name, reference type, or interface to work with different implementations.
Polymorphism is one of the fundamental principles of object-oriented programming.
Types of Polymorphism
| Type | Common Java Mechanism | Binding |
|---|---|---|
| Compile-Time Polymorphism | Method overloading | Compile time |
| Runtime Polymorphism | Method overriding | Runtime |
1. Compile-Time Polymorphism
Method overloading allows multiple methods to use the same name with different parameter lists.
class Calculator {
int add(int a, int b) {
return a + b;
}
double add(double a, double b) {
return a + b;
}
}
The compiler determines which overloaded method matches the call.
2. Runtime Polymorphism
Runtime polymorphism occurs when a superclass reference refers to a subclass object and an overridden instance method is selected according to the actual object at runtime.
class CiitCourse {
void study() {
System.out.println("Studying a general CIIT course 😊..");
}
}
class JavaCourse extends CiitCourse {
@Override
void study() {
System.out.println("Studying Java Full Stack Development at CIIT 🤓❤️...!");
}
}
CiitCourse course = new JavaCourse();
course.study();
Studying Java Full Stack Development at CIIT 🤓❤️...!
Dynamic Method Dispatch
The runtime selection of an overridden instance method is commonly called dynamic method dispatch.
CiitCourse course;
course = new JavaCourse();
course.study();
Although the reference type is Animal, the
actual object is a Dog, so the overridden
Dog.sound() implementation executes.
Upcasting
Assigning a subclass object to a superclass reference is called upcasting.
JavaCourse java = new JavaCourse();
CiitCourse course = java;
The reference can access members available through the
Animal type, while overridden methods can still
execute the subclass implementation.
Downcasting
Downcasting converts a superclass reference back to a subclass reference when the object is actually an instance of that subclass.
CiitCourse course = new JavaCourse();
JavaCourse java = (JavaCourse) course;
java.study();
ClassCastException.
Checking Types with instanceof
CiitCourse course = new JavaCourse();
if (course instanceof JavaCourse) {
JavaCourse java = (JavaCourse) course;
java.study();
}
Polymorphism with Interfaces
Interfaces are one of the most important ways to achieve flexible polymorphic designs in Java.
interface Payment {
void pay();
}
class CreditCardPayment implements Payment {
public void pay() {
System.out.println(
"Paid using credit card"
);
}
}
class UpiPayment implements Payment {
public void pay() {
System.out.println(
"Paid using UPI"
);
}
}
Interface Reference
Payment payment;
payment = new CreditCardPayment();
payment.pay();
payment = new UpiPayment();
payment.pay();
Paid using credit card
Paid using UPI
Polymorphism and Loose Coupling
Polymorphism allows code to depend on abstractions rather than concrete implementations.
static void processPayment(Payment payment) {
payment.pay();
}
The method can accept any object that implements
Payment.
Polymorphism with Abstract Classes
abstract class Shape {
abstract double area();
}
class Circle extends Shape {
double radius;
Circle(double radius) {
this.radius = radius;
}
@Override
double area() {
return Math.PI * radius * radius;
}
}
class Rectangle extends Shape {
double width;
double height;
Rectangle(double width, double height) {
this.width = width;
this.height = height;
}
@Override
double area() {
return width * height;
}
}
Abstract Reference and Runtime Behavior
Shape shape;
shape = new Circle(5);
System.out.println(shape.area());
shape = new Rectangle(10, 20);
System.out.println(shape.area());
The same Shape reference can refer to different
concrete objects.
Same Method Call, Different Behavior
Shape
│
├── Circle.area()
│
└── Rectangle.area()
shape.area()
↓
Actual object determines implementation
Method Overriding Rules
- The subclass method must have a compatible signature with the inherited method.
- The overriding method cannot reduce access visibility.
-
A method declared
finalcannot be overridden. - Static methods are hidden rather than overridden.
- Private methods are not overridden because they are not inherited by subclasses.
-
The
@Overrideannotation is recommended when overriding an inherited method.
Covariant Return Types
An overriding method can return a subtype of the return type declared by the superclass method.
class Animal {
Animal create() {
return new Animal();
}
}
class Dog extends Animal {
@Override
Dog create() {
return new Dog();
}
}
Overloading vs Overriding
| Feature | Overloading | Overriding |
|---|---|---|
| Class Relationship | Not required. | Usually involves inheritance. |
| Method Name | Same. | Same. |
| Parameters | Must differ. | Compatible signature. |
| Binding | Compile time. | Runtime for instance methods. |
| Purpose | Multiple forms of an operation. | Specialized subclass behavior. |
Polymorphism with Collections
A collection can store references of a common superclass or interface while containing objects of different concrete types.
List<Animal> animals = new ArrayList<>();
animals.add(new Dog());
animals.add(new Cat());
for (Animal animal : animals) {
animal.sound();
}
Real-World Example: Notification System
interface Notification {
void send(String message);
}
class EmailNotification implements Notification {
public void send(String message) {
System.out.println(
"Email: " + message
);
}
}
class SmsNotification implements Notification {
public void send(String message) {
System.out.println(
"SMS: " + message
);
}
}
class NotificationService {
void notifyUser(
Notification notification,
String message
) {
notification.send(message);
}
}
The service does not need to know the concrete notification
implementation. It works with the common
Notification abstraction.
Benefits of Polymorphism
- Supports flexible and extensible designs.
- Reduces coupling between components.
- Makes code easier to extend with new implementations.
- Enables common interfaces for different behaviors.
- Supports reusable service and business logic.
Polymorphism Best Practices
- Prefer interfaces or suitable abstractions when designing interchangeable implementations.
- Use inheritance only when the relationship is genuinely an IS-A relationship.
-
Use
@Overridewhen overriding methods. - Avoid unnecessary downcasting.
- Keep polymorphic implementations consistent with the contract of their parent type or interface.
Interview Questions
Summary
Polymorphism allows Java code to work with different implementations through common method names, superclass references, or interfaces. Method overloading provides compile-time polymorphism, while method overriding enables runtime polymorphism and dynamic method dispatch.