Skip to content
intermediatePhase 11 · Java OOP

Polymorphism

Master compile-time and runtime polymorphism through overloading and overriding.

1h
3 problems
Topic Progress0%

Overloading

Method Overloading (Compile-Time Polymorphism)

Multiple methods with same name but different parameters.

Basic Overloading

public class Calculator {
    // Different parameter types
    public int add(int a, int b) {
        return a + b;
    }
    
    public double add(double a, double b) {
        return a + b;
    }
    
    // Different number of parameters
    public int add(int a, int b, int c) {
        return a + b + c;
    }
}

Calculator calc = new Calculator();
System.out.println(calc.add(1, 2));        // 3
System.out.println(calc.add(1.5, 2.5));   // 4.0
System.out.println(calc.add(1, 2, 3));    // 6

Overloading Rules

public class Example {
    // Valid - different types
    public void method(int x) { }
    public void method(double x) { }
    
    // Valid - different count
    public void method(int x, int y) { }
    
    // Valid - different order
    public void method(String s, int i) { }
    public void method(int i, String s) { }
    
    // INVALID - same signature
    // public void method(int x) { }
    // public void method(int y) { }  // Same!
}

Return Type Doesn't Matter

public class Example {
    public int method(int x) { return x; }
    // public double method(int x) { return x; }  // COMPILE ERROR!
    // Return type alone cannot differentiate overloads
}

Constructor Overloading

public class Person {
    String name;
    int age;
    
    public Person() { this("Unknown"); }
    public Person(String name) { this(name, 0); }
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

Overriding

Method Overriding (Runtime Polymorphism)

Child class provides specific implementation of parent method.

Basic Overriding

public class Animal {
    public void makeSound() {
        System.out.println("Some sound");
    }
}

public class Dog extends Animal {
    @Override
    public void makeSound() {
        System.out.println("Woof!");
    }
}

public class Cat extends Animal {
    @Override
    public void makeSound() {
        System.out.println("Meow!");
    }
}

Animal animal = new Dog();
animal.makeSound();  // "Woof!"
animal = new Cat();
animal.makeSound();  // "Meow!"

Overriding Rules

public class Parent {
    public void method() { }
}

public class Child extends Parent {
    // Must have same name, parameters, return type
    // Access modifier can be same or less restrictive
    // Cannot throw new/broader checked exceptions
    
    @Override  // Optional but recommended
    public void method() { }
    
    // INVALID:
    // private void method() { }  // More restrictive
    // public int method() { }    // Different return type
    // public void method(int x) { }  // Different params (overloading)
}

@Override Annotation

public class Child extends Parent {
    @Override  // Helps catch errors
    public void method() { }
    
    @Override  // Compiler checks if actually overriding
    public void nonExistentMethod() { }  // COMPILE ERROR!
}

Covariant Return Types

public class Animal {
    public Animal create() {
        return new Animal();
    }
}

public class Dog extends Animal {
    @Override
    public Dog create() {  // Return type is Dog (covariant)
        return new Dog();
    }
}

Dynamic Method Dispatch

Dynamic Method Dispatch

The JVM determines which method to call at runtime based on actual object type.

Basic Example

public class Animal {
    public void makeSound() {
        System.out.println("Animal sound");
    }
}

public class Dog extends Animal {
    @Override
    public void makeSound() {
        System.out.println("Woof!");
    }
}

public class Cat extends Animal {
    @Override
    public void makeSound() {
        System.out.println("Meow!");
    }
}

// Dynamic dispatch
Animal animal;
animal = new Dog();
animal.makeSound();  // "Woof!" (Dog's version)

animal = new Cat();
animal.makeSound();  // "Meow!" (Cat's version)

Array of Parent Type

Animal[] animals = { new Dog(), new Cat(), new Animal() };

for (Animal a : animals) {
    a.makeSound();  // Each calls its own version
}
// Output:
// Woof!
// Meow!
// Animal sound

Method Parameters

public class Vet {
    public void checkup(Animal animal) {
        animal.makeSound();  // Calls actual type's method
    }
}

Vet vet = new Vet();
vet.checkup(new Dog());  // "Woof!"
vet.checkup(new Cat());  // "Meow!"

Polymorphism Benefits

// Without polymorphism - lots of if/else
public void makeAnimalSound(Animal animal) {
    if (animal instanceof Dog) {
        ((Dog) animal).makeSound();
    } else if (animal instanceof Cat) {
        ((Cat) animal).makeSound();
    }
}

// With polymorphism - clean code
public void makeAnimalSound(Animal animal) {
    animal.makeSound();  // Polymorphism handles it!
}

Instanceof Operator

instanceof Operator

Checks if an object is an instance of a specific class or interface.

Basic Usage

Animal animal = new Dog();

if (animal instanceof Dog) {
    System.out.println("It's a dog!");
}

if (animal instanceof Animal) {
    System.out.println("It's an animal!");
}

if (animal instanceof Cat) {
    System.out.println("It's a cat!");  // Not printed
}

Safe Casting

public void processAnimal(Animal animal) {
    if (animal instanceof Dog) {
        Dog dog = (Dog) animal;  // Safe cast
        dog.fetch();
    }
}

// Or with pattern matching (Java 16+)
if (animal instanceof Dog dog) {
    dog.fetch();  // No explicit cast needed
}

With null

Animal animal = null;

if (animal instanceof Dog) { }  // false (not exception!)
// null instanceof anything is always false

Interface Checking

public class Dog implements Pet, Serializable {
    // ...
}

Dog dog = new Dog();

if (dog instanceof Pet) { }      // true
if (dog instanceof Serializable) { }  // true
if (dog instanceof Object) { }   // true (everything is Object)

When to Use

// 1. When you need type-specific behavior
public void feed(Animal animal) {
    if (animal instanceof Dog) {
        ((Dog) animal).eatKibble();
    } else {
        animal.eat();
    }
}

// 2. When you can't avoid casting
public void serialize(Object obj) {
    if (obj instanceof Serializable) {
        // Safe to cast
    }
}

// 3. Prefer polymorphism over instanceof when possible

Practice Problems

0/3solved
Design File System
Polymorphism

Design a file system with files and directories.

Example:

Input: ls('/'), mkdir('/a'), addContentToFile('/a/b', 'content')

Output: [a]

File system with polymorphic components

Optimal Solution — O(n) time, O(n) space

Use abstract Entry class with File and Directory subclasses.

abstract class Entry {
    protected String name;
    abstract int getSize();
}
class File extends Entry {
    private String content;
    int getSize() { return content.length(); }
}
class Directory extends Entry {
    private Map<String, Entry> children;
    int getSize() { return children.values().stream().mapToInt(Entry::getSize).sum(); }
}

Edge Cases:

  • Empty directory
  • Nested files
  • Root path
Design Shapes
Polymorphism

Design shapes with area calculation using polymorphism.

Example:

Input: Circle(5).area(), Rectangle(4, 5).area()

Output: 78.54, 20

Different area calculations

Optimal Solution — O(1) time, O(1) space

Abstract Shape class with concrete subclasses.

abstract class Shape {
    abstract double area();
}
class Circle extends Shape {
    double radius;
    double area() { return Math.PI * radius * radius; }
}
class Rectangle extends Shape {
    double width, height;
    double area() { return width * height; }
}

Edge Cases:

  • Zero dimensions
  • Negative values
  • Very large values
Design Payment System
Polymorphism

Design a payment system with different payment methods.

Example:

Input: pay(CreditCard), pay(PayPal)

Output: processed

Different payment processing

Optimal Solution — O(1) time, O(1) space

Abstract Payment class with concrete payment methods.

abstract class Payment {
    abstract void process(double amount);
}
class CreditCard extends Payment {
    void process(double amount) { /* process card */ }
}
class PayPal extends Payment {
    void process(double amount) { /* process PayPal */ }
}

Edge Cases:

  • Invalid amount
  • Failed payment
  • Different currencies

Quiz

1. What is method overloading?

Question 1 options

2. What is method overriding?

Question 2 options

3. What does instanceof check?

Question 3 options

4. When does dynamic method dispatch occur?

Question 4 options

Flashcards

Question

What is the difference between overloading and overriding?

Answer

Overloading: same name, different params (compile-time). Overriding: same method, different implementation (runtime).

Question

What is dynamic method dispatch?

Answer

JVM determines which method to call at runtime based on actual object type, not reference type.

Question

What does @Override do?

Answer

Optional annotation that tells compiler to verify the method actually overrides a parent method.

Question

What is polymorphism?

Answer

Ability of objects to take many forms - same reference type can point to different object types.

Question

What is Polymorphism?

Answer

Polymorphism is a key concept in Java programming.

Revision Notes

Key Takeaways

  • 1.Overloading is compile-time, overriding is runtime
  • 2.Dynamic dispatch enables polymorphism
  • 3.instanceof checks object type
  • 4.@Override prevents errors

Interview Tips

  • Know difference between overloading and overriding
  • Understand dynamic method dispatch
  • Use instanceof for type checking
  • Prefer polymorphism over type casting

Cheat Sheet

Cheat Sheet

  • Overloading: same name, different params
  • Overriding: same method, child implementation
  • @Override: annotation for safety
  • instanceof: type checking
  • Dynamic dispatch: runtime method selection