JavaPolymorphism

Polymorphism

What Is Polymorphism?

Polymorphism means "many forms." In OOP, it describes a single method call or a single interface that behaves differently depending on what it's actually operating on. You call the exact same method, through the exact same reference type, but the behavior that runs depends on the real, runtime type of the object underneath.

Java gives you polymorphism in two distinct flavors: one resolved while the code is being compiled, and one resolved while the program is actually running.

Kind

Mechanism

Resolved

Compile-time (static) polymorphism

Method overloading — multiple methods with the same name but different parameter lists

At compile time, based on the arguments' declared types

Runtime (dynamic) polymorphism

Method overriding — a subclass replaces an inherited method's implementation

At runtime, based on the object's actual type

Compile-Time Polymorphism: Overloading

Method overloading lets a class define several methods with the same name but different parameter lists. The compiler decides, at compile time, exactly which overload a given call matches, purely by looking at the number and types of the arguments.

Overloading — resolved at compile time

Java
public class Calculator {
    int add(int a, int b) {
        return a + b;
    }

    double add(double a, double b) {
        return a + b;
    }
}
Note
Method overloading has its own dedicated page with the full set of rules for how the compiler picks between overloads. The important point here is that the choice is locked in when the code compiles — it never depends on what happens while the program runs.
Runtime Polymorphism: Overriding

Runtime polymorphism comes from method overriding: a subclass provides its own implementation of a method that's already defined in its superclass (or an interface it implements). When you call that method through a superclass-typed reference, Java looks at the object's actual runtime type to decide which version to run — not the type of the variable holding the reference. This lookup is called dynamic dispatch, and it is the mechanism the rest of this page demonstrates.

Note
The mechanics of overriding — matching signatures, the @Override annotation, and the rules that govern it — are covered in full on the method-overriding page.
Worked Example: Dynamic Dispatch

Here, three different subclasses override the same makeSound() method. A single array of the superclass type Animal holds objects of every subclass, and calling makeSound() on each element produces different output — even though every call in the loop is written identically.

One method call, three different behaviors

Java
public class Animal {
    void makeSound() {
        System.out.println("The animal makes a sound");
    }
}

public class Dog extends Animal {
    @Override
    void makeSound() {
        System.out.println("The dog barks");
    }
}

public class Cat extends Animal {
    @Override
    void makeSound() {
        System.out.println("The cat meows");
    }
}

public class Cow extends Animal {
    @Override
    void makeSound() {
        System.out.println("The cow moos");
    }
}

public class Main {
    public static void main(String[] args) {
        Animal[] animals = { new Dog(), new Cat(), new Cow() };

        for (Animal a : animals) {
            a.makeSound(); // same call, different behavior each time
        }
    }
}
The dog barks
The cat meows
The cow moos

Every element of the animals array is declared as type Animal, and every loop iteration calls the exact same expression, a.makeSound(). Yet the output differs each time, because Java resolves which makeSound() to actually run based on each object's real, underlying type (Dog, Cat, or Cow) rather than the declared reference type (Animal). That's runtime polymorphism in action.

Why Polymorphism Matters
  • You can write code against a general superclass or interface type and have it automatically work correctly for any subclass, present or future

  • Adding a new subclass (say, a Bird that overrides makeSound()) requires no changes to the loop in Main — it just works

  • This is the foundation for extensible designs: frameworks, plugin systems, and collections of mixed but related objects all lean on runtime polymorphism