Polymorphism (from the Greek words for "many shapes") is one of the pillars of Object-Oriented Programming (OOP). It allows us to treat objects of different child classes as if they were objects of their shared parent class, while still letting each object behave in its own unique way at runtime.
Let's look at how Java handles polymorphic method dispatch using a simple theater actor analogy.
Imagine a director writing a theater script. The script has a role called "Actor" (the
Parent Class A) with directions like "give speech" (method m1()) and
"take a bow" (method m2()).
When the play begins, the director hires a specific actor named "Bob" (the Child Class B) to play the role. The play unfolds like this:
- The script says:
Actor a = new Bob();(The director refers to Bob simply as "the actor" on stage). - The script commands the actor to:
a.m1();("give speech"). Even though the script is written for a generic Actor, Bob delivers his own custom lines that he rehearsed (B's overridden methodm1()executes). - The script commands the actor to:
a.m2();("take a bow"). Since Bob didn't prepare a custom bow, he performs the standard bow that all actors are trained to do (A's inherited methodm2()executes).
Walkthrough of the Main Method Scenario
Let's trace how the program executes step-by-step from the entry point of the main method:
The program declares a reference variable a of type A, but points it to an
instance of class B on the heap:
A a = new B();
At compile time, the compiler only checks if class A has the method we want to call. Since
class A has m1(), the code compiles successfully.
Next, the program calls a.m1():
- At runtime, the JVM looks at the actual object on the heap, which is of type
B. - It checks if class
Boverridesm1(). It does! - Thus, it invokes
B's version of the method, printing: **B m1()**.
Then, the program declares a reference and object both of child type B:
B b = new B();
b.m1();
Since both the reference and the object are of type B, the JVM directly calls B's
m1() method, printing: **B m1()**.
Notice that Parent Class A declares protected int x = 10;. If Child Class
B were to declare its own int x = 20;, variables do not override each other (this
is called variable hiding). If we access a.x, it would print 10
because field access is resolved at compile time based on the reference type (A), not the
actual object type!
Java Implementation
Below is the complete Java code demonstrating polymorphism and class method inheritance:
package io.practise.accolite;
public class InheritanceChecker {
public static void main(String[] args) {
A a = new B();
a.m1(); // Prints: B m1()
B b = new B();
b.m1(); // Prints: B m1()
}
}
class A {
protected int x = 10;
public void m1() {
System.out.println("A m1()");
}
public void m2() {
System.out.println("A m2()");
}
}
class B extends A {
@Override
public void m1() {
System.out.println("B m1()");
}
}
Conclusion
Polymorphism allows our code to be flexible and extensible. By writing code that references the parent class, we can easily swap in new child classes in the future without changing our main logic. The JVM will automatically look up the correct overridden method implementation at runtime!