100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
Java

Polymorphism in Java

Explore compile-time and runtime polymorphism in Java, including method overloading, overriding, and dynamic dispatch.

OOP BasicsIntermediate10 min readJul 7, 2026
Analogies

1. Introduction

Polymorphism, meaning 'many forms,' allows a single interface or method name to behave differently depending on the context. In Java, polymorphism appears in two forms: compile-time (static) polymorphism, achieved through method overloading, and runtime (dynamic) polymorphism, achieved through method overriding.

🏏

Cricket analogy: Polymorphism means 'many forms', like how a bowler's role changes shape—a spinner chosen before the match based on the pitch (compile-time, like overloading) versus a bowler actually adjusting their line mid-over based on the batsman facing them (runtime, like overriding).

Runtime polymorphism is closely tied to inheritance: a superclass reference variable can hold a reference to a subclass object, and the actual method executed is determined at runtime based on the object's real type — this is called dynamic method dispatch.

🏏

Cricket analogy: Runtime polymorphism is like an 'All-Rounder' contract slot on a team sheet that can actually hold a specific player like Ravindra Jadeja or Hardik Pandya, and which player's actual bowling action executes is decided live during the over — dynamic method dispatch on the field.

2. Syntax

java
// Compile-time polymorphism: method overloading
class Calc {
    int add(int a, int b) { return a + b; }
    double add(double a, double b) { return a + b; }
}

// Runtime polymorphism: method overriding
class Animal {
    void sound() { System.out.println("Animal makes a sound"); }
}
class Dog extends Animal {
    @Override
    void sound() { System.out.println("Dog barks"); }
}

// Upcasting
Animal a = new Dog();
a.sound(); // Dog's overridden version runs (dynamic dispatch)

3. Explanation

Method overloading is resolved at compile time based on the method signature (number/type of parameters) — the compiler decides which version to call. Method overriding, however, is resolved at runtime: the JVM looks at the actual object type (not the reference type) to decide which overridden method implementation to execute, a process known as dynamic method dispatch.

🏏

Cricket analogy: Overloading is the selectors picking a squad before the tour based on the player names on the list (compile-time, resolved by signature), while overriding is the umpire's actual lbw decision made live based on the real trajectory of the ball — dynamic method dispatch at runtime.

Upcasting (assigning a subclass object to a superclass reference, e.g., Animal a = new Dog();) is implicit and safe. Downcasting (converting a superclass reference back to a subclass type, e.g., Dog d = (Dog) a;) must be explicit and can throw a ClassCastException at runtime if the object is not actually an instance of the target subclass.

🏏

Cricket analogy: Upcasting is like listing a specialist spinner simply as 'Bowler' on the scoreboard—safe and automatic—while downcasting is explicitly claiming that generic 'Bowler' is specifically the spinner Kuldeep Yadav, which fails with a ClassCastException-like error if it's actually a pacer.

Exam trap: With overriding, the method that runs depends on the object's actual runtime type, NOT the reference type. With overloading, the version called depends on the reference/compile-time argument types.

4. Example

java
public class Shape {
    void draw() {
        System.out.println("Drawing a generic shape");
    }
}

class Circle extends Shape {
    @Override
    void draw() {
        System.out.println("Drawing a circle");
    }
}

class Square extends Shape {
    @Override
    void draw() {
        System.out.println("Drawing a square");
    }
}

class Demo {
    public static void main(String[] args) {
        Shape[] shapes = { new Circle(), new Square(), new Shape() };
        for (Shape s : shapes) {
            s.draw();  // runtime polymorphism via dynamic dispatch
        }

        // downcasting example
        Shape s = new Circle();
        Circle c = (Circle) s;  // safe: s actually refers to a Circle
        c.draw();
    }
}

5. Output

text
Drawing a circle
Drawing a square
Drawing a generic shape
Drawing a circle

6. Key Takeaways

  • Compile-time polymorphism (method overloading) is resolved by the compiler based on method signature.
  • Runtime polymorphism (method overriding) is resolved by the JVM based on the object's actual type.
  • A superclass reference can hold a subclass object (upcasting), enabling dynamic dispatch.
  • Downcasting must be explicit and can throw ClassCastException if the types are incompatible.
  • Dynamic method dispatch means the overridden method of the actual object runs, not the reference's declared type.

Practice what you learned

Was this page helpful?

Topics covered

#Java#JavaProgrammingStudyNotes#Programming#PolymorphismInJava#Polymorphism#Syntax#Explanation#Example#OOP#StudyNotes#SkillVeris