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!
// Parent classclass 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)
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)
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
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
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
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