While the main principle of polymorphism is decoupling "what from who" in term of types
, but what confuses me how does method-call mechanism finds out and calls the correct method body in polymorphism.
Since in java all method binding is late-binding
unless the method is static
, final
or private
, and late-binding is done by JVM which precomputes method table
for each class and then do a table look up during runtime in normal method call.
But the same thing happens during polymorphism too. For example
Suppose I've a Generic class Cycle
with a ride()
method
class Cycle {
public void ride(){
System.out.println("I'm Riding generic Cycle()");
}
}
And i have three Specialized Class Bicycle
Tricycle
and Unicycle
which extends Generic class Cycle
and overrides its ride()
method.
class Bicycle extends Cycle {
public void ride() {
System.out.println("I'm riding Bicycle");
}
}
class Tricycle extends Cycle{
public void ride() {
System.out.println("I'm riding Tricycle ");
}
}
class Unicycle extends Cycle {
public void ride() {
System.out.println("I'm Riding Unicycle ");
}
}
This is the TestRide
class to Test the above Polymorphism.
public class TestRide {
public static void ride(Cycle c){
c.ride();
}
public static void main(String[] args){
Cycle Cycling = new Cycle();
ride(Cycling);
Bicycle bi = new Bicycle();
ride(bi);
Tricycle tri = new Tricycle();
ride(tri);
Unicycle uni = new Unicycle();
ride(uni);
}
}
The Output is
I'm Riding generic Cycle()
I'm riding Bicycle
I'm riding Tricycle
I'm Riding Unicycle
Byte Code:
public static void main(java.lang.String[]);
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=5, args_size=1
0: new #17 // class com/polymorphism/Cycle
3: dup
4: invokespecial #24 // Method com/polymorphism/Cycle."
<init>":()V
7: astore_1
8: aload_1
9: invokestatic #25 // Method ride:(Lcom/polymorphism/
Cycle;)V
12: new #27 // class com/polymorphism/Bicycle
15: dup
16: invokespecial #29 // Method com/polymorphism/Bicycle
."<init>":()V
19: astore_2
20: aload_2
21: invokestatic #25 // Method ride:(Lcom/polymorphism/
Cycle;)V
24: new #30 // class com/polymorphism/Tricycle
27: dup
28: invokespecial #32 // Method com/polymorphism/Tricycl
e."<init>":()V
31: astore_3
32: aload_3
33: invokestatic #25 // Method ride:(Lcom/polymorphism/
Cycle;)V
36: new #33 // class com/polymorphism/Unicycle
39: dup
40: invokespecial #35 // Method com/polymorphism/Unicycl
e."<init>":()V
43: astore 4
45: aload 4
47: invokestatic #25 // Method ride:(Lcom/polymorphism/
Cycle;)V
50: return
Even in the bytecode its just as usual method call with invokestatic
and invokespecial
while i thought it would use invokedynamic
to figure out the version of the method that is appropriate for the actual type of the object. But that was not the case.
So how does Java figure out the actual method call during polymorphism while we just pass an upcasted Object in the ride()
method like ride(bi)
in TestRide
class ?
EDIT: RIDE method ByteCode
public static void ride(com.polymorphism.Cycle);
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokevirtual #16 // Method com/polymorphism/Cycle.r
ide:()V
4: return
First off
invokedynamic
is for Java 8 lambdas and non-Java code, so you can forget about that.Apart from that, there are four invoke instructions (
invokespecial
,invokestatic
,invokevirtual
, andinvokeinterface
). You can see the precise semantics in the JVM sepcification, but the bottom line is thatinvokevirtual
andinvokeinterface
are virtual method calls, i.e. the actual method called is chosen at runtime based on the conrete type of the target.The only virtual call in your code is in TestRide.ride. The listed target is
Cycle.ride:()V
. However, since it is a virtual call, the JVM will check the actual type of the first argument at runtime and call the most derived version of that method.This is similar to virtual method calls in C++, except that the abstraction of the JVM and JIT compilation allows the potential for more optimized implementations.
Also note that this is not to be confused with method overloading, which is a form of compile-time polymorphism. For overloaded methods, the compiler chooses which one to call based on the compile time type of the arguments.
I think @JBNizet figured out the solution already in the comments (and my guess turned out to be wrong). But since he doesn't post it as an answer, I'll do it:
The
main
method isn't supposed to show any dynamic behavior because it is always ever going to call that one single methodTestRide.ride(Cycle c)
. There is no other possible method, so nothing to figure out.The dynamic method call is inside that method
TestRide.ride(Cycle c)
. And now that you posted that code, indeed we see a dynamic method dispatch usinginvokevirtual
. So, after all, no surprises. The dynamic method dispatch is there.