Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How does Java Determine methods call at runtime in polymorphism?

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
like image 222
Ankur Anand Avatar asked Sep 20 '15 11:09

Ankur Anand


1 Answers

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, and invokeinterface). You can see the precise semantics in the JVM sepcification, but the bottom line is that invokevirtual and invokeinterface 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.

like image 56
Antimony Avatar answered Oct 29 '22 04:10

Antimony