2015-09-20 4 views
5

В то время как основной принцип полиморфизма заключается в развязывании «что из кого» в терминах types, но что меня смущает, как выясняется механизм вызова метода и вызывает правильное тело метода в полиморфизме.Как Java Определяет методы вызова во время выполнения в полиморфизме?

Поскольку в Java все метод связывания является late-binding, если метод не является static, final или private и позднего связывания выполняется JVM, который предварительно вычисляет method table для каждого класса, а затем сделать таблицу просмотра во время выполнения в обычном вызове метода.

Но то же самое происходит и при полиморфизме. Например

Предположим, что У меня есть универсальный класс Cycle с ride() методом

class Cycle { 

    public void ride(){ 
     System.out.println("I'm Riding generic Cycle()"); 
    } 

} 

И у меня есть три специализированного класса BicycleTricycle и Unicycle, который расширяет универсальный класс Cycle и переопределяет его метод ride().

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 "); 

    } 

} 

Это TestRide класс Протестируйте выше полиморфизм.

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); 
    } 

} 

Выход является

I'm Riding generic Cycle() 
I'm riding Bicycle 
I'm riding Tricycle 
I'm Riding Unicycle 

Байт Код:

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 

Даже в байткод его так же, как обычный вызов метода с invokestatic и invokespecial в то время как я думал, что он будет использовать invokedynamic на рисунке выведите версию метода, подходящую для фактического типа объекта. Но это было не так.

Так как же Java выяснить фактический вызов метода во время полиморфизма пока мы просто передать upcasted объект в ride() метод как ride(bi) в TestRide классе?

EDIT: RIDE метод 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 
+1

Возможно, ваш тестовый пример слишком прост. Если компилятор Java знает во время компиляции, какой метод будет вызываться, нет необходимости в байт-коде, который делает что-то динамически. – mastov

+1

'invokedynamic' был введен в байт-код JVM для динамического поиска методов, который * не * следит за поиском метода Java. Почему компилятор Java использует его? Отправка виртуального метода выполняется с помощью 'invokevirtual' и' invokeinterface'. Обратите внимание, что Java-компилятор, конечно, может свободно использовать 'invokedynamic', если захочет. Спецификация языка Java ничего не говорит о том, как должна быть скомпилирована Java, она даже не говорит, что ее нужно скомпилировать вообще, ее можно так же интерпретировать. –

+0

@ JörgWMittag Получил вашу точку :). Но вопрос о том, как он это делает, остается –

ответ

0

Я думаю @JBNizet понял, решение уже в комментариях (и мое предположение оказалось неверным). Но так как он не отправляет его в качестве ответа, я сделаю это:

Метод main не должен показывать любое динамическое поведение, потому что она всегда когда-либо буду называть, что одинодного метода TestRide.ride(Cycle c). Другого возможного метода нет, так что ничего не понять.

Динамический вызов метода внутри этого метода TestRide.ride(Cycle c). И теперь, когда вы разместили этот код, мы видим динамическую отправку методов, используя invokevirtual. Так что, в конце концов, никаких сюрпризов. Отправка динамического метода есть.

1

Прежде всего invokedynamic предназначен для Java 8 lambdas и не-Java-кода, поэтому вы можете забыть об этом.

Кроме того, есть четыре команды Invoke (invokespecial, invokestatic, invokevirtual и invokeinterface). Вы можете увидеть точную семантику в JVM sepcification, но в нижней строке указано, что invokevirtual и invokeinterface являются виртуальными вызовами методов, то есть фактический вызванный метод выбирается во время выполнения на основе типа коннекта цели.

Единственный виртуальный вызов в вашем коде находится в TestRide.ride. Указанная цель - Cycle.ride:()V. Однако, поскольку это виртуальный вызов, JVM проверит фактический тип первого аргумента во время выполнения и вызовет самую производную версию этого метода.

Это похоже на вызовы виртуальных методов на C++, за исключением того, что абстракция JVM и JIT-компиляции позволяет использовать более оптимизированные реализации.

Также обратите внимание, что это не следует путать с перегрузкой метода, которая является формой полиморфизма времени компиляции. Для перегруженных методов компилятор выбирает, какой из них вызывать на основе типа времени компиляции аргументов.