Runtime polymorphism (also called dynamic polymorphism) is resolved while the program is actually running, not by the compiler ahead of time. In Java, this is achieved through method overriding — a subclass provides its own implementation of a method already defined in its parent class.
Tip: this is what lets you write code against a parent type (like Animal) while the actual behavior that runs depends on the real object's subclass (like Dog or Cat) at runtime.
Method overriding
class Animal {
void makeSound() {
System.out.println("Some generic animal sound");
}
}
class Dog extends Animal {
@Override
void makeSound() {
System.out.println("Woof!");
}
}
Animal myPet = new Dog();
myPet.makeSound(); // prints "Woof!" — not the Animal version
Even though myPet is declared as type Animal, Java looks at the actual object at runtime (a Dog) to decide which makeSound() implementation to call. That decision can’t be made until the program is actually running — hence “runtime” polymorphism.
Compile-time vs runtime, side by side
| Compile-time (overloading) | Runtime (overriding) | |
|---|---|---|
| Resolved by | The compiler | The JVM, while running |
| Achieved via | Multiple methods, same name, different parameters | Subclass redefining a parent method |
| Also known as | Static polymorphism | Dynamic polymorphism |
What’s next
That wraps up Polymorphism. Time for a quick section quiz before moving on.