Polymorphism in Java

Learn how one thing can take many forms

💡 Think of it like a TV remote - one button (method) does different things on different devices! The 'play' button plays TV shows, plays music, or plays games depending on which device you're using. Same button name, different actions! That's polymorphism!

🎭 What is Polymorphism?

Polymorphism means 'many forms'. It allows objects of different classes to be treated as objects of a common parent class. The same method name can behave differently depending on which object calls it!

PolymorphismBasics.java
java
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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
// Parent class
class Animal {
public void makeSound() {
System.out.println("Some generic animal sound");
}
public void sleep() {
System.out.println("Zzz...");
}
}
// Child classes - each with their own version of makeSound()
class Dog extends Animal {
@Override
public void makeSound() {
System.out.println("Woof! Woof!");
}
}
class Cat extends Animal {
@Override
public void makeSound() {
System.out.println("Meow! Meow!");
}
}
class Cow extends Animal {
@Override
public void makeSound() {
System.out.println("Moo! Moo!");
}
}
// Polymorphism in action!
class PolymorphismDemo {
public static void main(String[] args) {
// Same type (Animal), different objects
Animal myAnimal = new Animal();
Animal myDog = new Dog(); // Dog IS-A Animal
Animal myCat = new Cat(); // Cat IS-A Animal
Animal myCow = new Cow(); // Cow IS-A Animal
// Same method call, different behaviors!
myAnimal.makeSound(); // "Some generic animal sound"
myDog.makeSound(); // "Woof! Woof!"
myCat.makeSound(); // "Meow! Meow!"
myCow.makeSound(); // "Moo! Moo!"
// This is polymorphism - one interface, many forms!
}
}

🔀 Types of Polymorphism

1. Compile-time Polymorphism (Method Overloading)

Same method name with different parameters in the same class

add(int a, int b) and add(int a, int b, int c)

2. Runtime Polymorphism (Method Overriding)

Child class provides its own implementation of parent's method

Parent: makeSound() | Dog: bark() | Cat: meow()

⚡ Method Overloading (Compile-time Polymorphism)

MethodOverloading.java
java
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
32
33
34
35
36
37
38
39
40
41
42
43
44
class Calculator {
// Same method name "add", different parameters
// Method 1: Add two integers
public int add(int a, int b) {
System.out.println("Adding two integers");
return a + b;
}
// Method 2: Add three integers
public int add(int a, int b, int c) {
System.out.println("Adding three integers");
return a + b + c;
}
// Method 3: Add two doubles
public double add(double a, double b) {
System.out.println("Adding two doubles");
return a + b;
}
// Method 4: Add two strings (concatenate)
public String add(String a, String b) {
System.out.println("Concatenating strings");
return a + b;
}
}
class OverloadingDemo {
public static void main(String[] args) {
Calculator calc = new Calculator();
// Java chooses the right method based on arguments!
int result1 = calc.add(5, 10); // Calls method 1
int result2 = calc.add(5, 10, 15); // Calls method 2
double result3 = calc.add(5.5, 10.5); // Calls method 3
String result4 = calc.add("Hello", "World"); // Calls method 4
System.out.println("Result 1: " + result1);
System.out.println("Result 2: " + result2);
System.out.println("Result 3: " + result3);
System.out.println("Result 4: " + result4);
}
}

🔄 Method Overriding (Runtime Polymorphism)

MethodOverriding.java
java
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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
class Shape {
protected String color;
public Shape(String color) {
this.color = color;
}
// This method will be overridden by child classes
public double calculateArea() {
return 0;
}
public void display() {
System.out.println("This is a " + color + " shape");
System.out.println("Area: " + calculateArea());
}
}
class Circle extends Shape {
private double radius;
public Circle(String color, double radius) {
super(color);
this.radius = radius;
}
@Override
public double calculateArea() {
return Math.PI * radius * radius;
}
}
class Rectangle extends Shape {
private double width;
private double height;
public Rectangle(String color, double width, double height) {
super(color);
this.width = width;
this.height = height;
}
@Override
public double calculateArea() {
return width * height;
}
}
class Triangle extends Shape {
private double base;
private double height;
public Triangle(String color, double base, double height) {
super(color);
this.base = base;
this.height = height;
}
@Override
public double calculateArea() {
return 0.5 * base * height;
}
}
class ShapeDemo {
public static void main(String[] args) {
// All stored as Shape type (parent)
Shape shape1 = new Circle("Red", 5.0);
Shape shape2 = new Rectangle("Blue", 4.0, 6.0);
Shape shape3 = new Triangle("Green", 3.0, 4.0);
// Same method call, different calculations!
shape1.display(); // Calculates circle area
System.out.println();
shape2.display(); // Calculates rectangle area
System.out.println();
shape3.display(); // Calculates triangle area
}
}

🎯 Dynamic Method Dispatch

DynamicDispatch.java
java
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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
class Payment {
protected double amount;
public Payment(double amount) {
this.amount = amount;
}
public void processPayment() {
System.out.println("Processing generic payment: $" + amount);
}
}
class CreditCardPayment extends Payment {
private String cardNumber;
public CreditCardPayment(double amount, String cardNumber) {
super(amount);
this.cardNumber = cardNumber;
}
@Override
public void processPayment() {
System.out.println("Processing Credit Card payment");
System.out.println("Card: ****" + cardNumber.substring(12));
System.out.println("Amount: $" + amount);
System.out.println("Payment successful!");
}
}
class PayPalPayment extends Payment {
private String email;
public PayPalPayment(double amount, String email) {
super(amount);
this.email = email;
}
@Override
public void processPayment() {
System.out.println("Processing PayPal payment");
System.out.println("Email: " + email);
System.out.println("Amount: $" + amount);
System.out.println("Payment successful!");
}
}
class CashPayment extends Payment {
public CashPayment(double amount) {
super(amount);
}
@Override
public void processPayment() {
System.out.println("Processing Cash payment");
System.out.println("Amount: $" + amount);
System.out.println("Payment received!");
}
}
class PaymentSystem {
// This method accepts ANY type of Payment!
public static void handlePayment(Payment payment) {
// Java decides at RUNTIME which processPayment() to call
payment.processPayment();
System.out.println("---");
}
public static void main(String[] args) {
// Create different payment types
Payment payment1 = new CreditCardPayment(150.00, "1234567890123456");
Payment payment2 = new PayPalPayment(75.50, "user@email.com");
Payment payment3 = new CashPayment(50.00);
// Handle all payments the same way!
// The correct method is called based on actual object type
handlePayment(payment1); // Calls CreditCardPayment's method
handlePayment(payment2); // Calls PayPalPayment's method
handlePayment(payment3); // Calls CashPayment's method
// This is the power of polymorphism!
}
}

🌟 Real-World Example: Drawing Application

DrawingApp.java
java
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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
abstract class Drawable {
protected int x, y;
protected String color;
public Drawable(int x, int y, String color) {
this.x = x;
this.y = y;
this.color = color;
}
// Abstract method - must be implemented by children
public abstract void draw();
public void moveTo(int newX, int newY) {
this.x = newX;
this.y = newY;
System.out.println("Moved to (" + x + ", " + y + ")");
}
}
class Circle extends Drawable {
private int radius;
public Circle(int x, int y, String color, int radius) {
super(x, y, color);
this.radius = radius;
}
@Override
public void draw() {
System.out.println("Drawing a " + color + " circle");
System.out.println("Position: (" + x + ", " + y + ")");
System.out.println("Radius: " + radius);
}
}
class Rectangle extends Drawable {
private int width, height;
public Rectangle(int x, int y, String color, int width, int height) {
super(x, y, color);
this.width = width;
this.height = height;
}
@Override
public void draw() {
System.out.println("Drawing a " + color + " rectangle");
System.out.println("Position: (" + x + ", " + y + ")");
System.out.println("Size: " + width + "x" + height);
}
}
class Line extends Drawable {
private int endX, endY;
public Line(int x, int y, String color, int endX, int endY) {
super(x, y, color);
this.endX = endX;
this.endY = endY;
}
@Override
public void draw() {
System.out.println("Drawing a " + color + " line");
System.out.println("From: (" + x + ", " + y + ")");
System.out.println("To: (" + endX + ", " + endY + ")");
}
}
class Canvas {
private Drawable[] shapes;
private int shapeCount;
public Canvas(int maxShapes) {
shapes = new Drawable[maxShapes];
shapeCount = 0;
}
public void addShape(Drawable shape) {
if (shapeCount < shapes.length) {
shapes[shapeCount++] = shape;
}
}
// Draw all shapes - polymorphism in action!
public void drawAll() {
System.out.println("=== Drawing Canvas ===");
for (int i = 0; i < shapeCount; i++) {
shapes[i].draw(); // Calls the correct draw() method!
System.out.println();
}
}
}
class DrawingDemo {
public static void main(String[] args) {
Canvas canvas = new Canvas(10);
// Add different shapes to canvas
canvas.addShape(new Circle(100, 100, "Red", 50));
canvas.addShape(new Rectangle(200, 150, "Blue", 80, 60));
canvas.addShape(new Line(50, 50, "Green", 250, 250));
canvas.addShape(new Circle(300, 300, "Yellow", 30));
// Draw everything - each shape draws itself correctly!
canvas.drawAll();
}
}

📦 Polymorphism with Arrays

PolymorphicArray.java
java
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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
class Vehicle {
protected String brand;
public Vehicle(String brand) {
this.brand = brand;
}
public void start() {
System.out.println(brand + " vehicle is starting");
}
}
class Car extends Vehicle {
public Car(String brand) {
super(brand);
}
@Override
public void start() {
System.out.println(brand + " car engine starting... Vroom!");
}
}
class Motorcycle extends Vehicle {
public Motorcycle(String brand) {
super(brand);
}
@Override
public void start() {
System.out.println(brand + " motorcycle engine starting... Brrrm!");
}
}
class Truck extends Vehicle {
public Truck(String brand) {
super(brand);
}
@Override
public void start() {
System.out.println(brand + " truck engine starting... ROAR!");
}
}
class Garage {
public static void main(String[] args) {
// Array of parent type can hold any child objects!
Vehicle[] garage = new Vehicle[5];
garage[0] = new Car("Toyota");
garage[1] = new Motorcycle("Harley");
garage[2] = new Truck("Ford");
garage[3] = new Car("Honda");
garage[4] = new Motorcycle("Yamaha");
// Start all vehicles
System.out.println("Starting all vehicles in garage:");
for (Vehicle vehicle : garage) {
vehicle.start(); // Each calls its own version!
}
// This is polymorphism - treat different types uniformly!
}
}

🔑 Key Concepts

Method Overloading

Multiple methods with same name but different parameters

print(int x), print(String s), print(int x, int y)

Method Overriding

Child class redefines parent's method

@Override public void draw() { }

Dynamic Method Dispatch

Java decides which method to call at runtime

Animal a = new Dog(); a.sound(); // Calls Dog's sound()

Upcasting

Treating child object as parent type

Animal animal = new Dog(); // Dog IS-A Animal

Best Practices

  • Use @Override annotation when overriding methods
  • Keep method signatures consistent across inheritance hierarchy
  • Use polymorphism to write flexible, maintainable code
  • Prefer polymorphism over if-else chains for type checking
  • Remember: Overloading happens at compile-time, Overriding at runtime

💼 Interview Tips

  • Polymorphism enables 'one interface, many implementations'
  • Method overloading is resolved at compile-time (static binding)
  • Method overriding is resolved at runtime (dynamic binding)
  • Overloaded methods must differ in parameter list (number or types)
  • Overridden methods must have the same signature as parent method
  • Private, static, and final methods cannot be overridden