2015-09-20 13 views
5

Podczas gdy główną zasadą polimorfizmu jest oddzielenie "tego, od kogo" pod kątem types, ale co wprowadza mnie w zakłopotanie, w jaki sposób mechanizm wywoływania metody dowiaduje się i nazywa prawidłowe ciało metody w polimorfizmie.W jaki sposób metoda Java Determine wywołuje w środowisku wykonawczym polimorfizm?

Ponieważ w Javie wszystkie metody wiązania jest late-binding chyba że metoda jest static, final lub private i późno wiążącego odbywa się przez JVM które precomputes method table dla każdej klasy, a następnie zrobić stół patrzeć podczas wykonywania w normalnym wywołaniu metody.

Ale to samo dzieje się podczas polimorfizmu. Na przykład

Załóżmy Mam Generic klasy Cycle z ride() metody

class Cycle { 

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

} 

i mam trzy klasy specjalistyczne BicycleTricycle i Unicycle która rozciąga Generic klasy Cycle i nadpisuje jego metodę 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 "); 

    } 

} 

Jest to klasa TestRide testować powyższy polimorfizm.

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

} 

wyjście jest kod

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

Byte:

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 

Nawet w kodu bajtowego jej po prostu jako zwykłe wywołanie metody z invokestatic i invokespecial gdy myślałem, że użyłby invokedynamic figury wersję metody odpowiednią dla faktycznego typu obiektu. Ale tak nie było.

W jaki sposób Java wykrywa rzeczywiste wywołanie metody podczas polimorfizmu, podczas gdy my po prostu przekazujemy obiekt upcasted w metodzie ride(), takiej jak ride(bi) w klasie TestRide?

EDIT: RIDE metoda kodu bajtowego

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

Może twój przypadek testowy jest zbyt prosty. Jeśli kompilator Java wie w czasie kompilacji, jaka metoda zostanie wywołana, nie ma potrzeby, by kod bajtowy robił cokolwiek dynamicznie. – mastov

+1

'invokedynamic' został wprowadzony w kodzie bajtowym JVM do dynamicznego wyszukiwania metod, które * nie * jest zgodne z wyszukiwaniem metody Java. Dlaczego używałby go kompilator Java? Wywołanie metody wirtualnej odbywa się za pomocą 'invokevirtual' i' invokeinterface'. Zauważ, że kompilator Java może oczywiście używać 'invokedynamic', jeśli chce. Specyfikacja języka Java nie mówi nic o tym, jak należy kompilować Javę, nie mówi nawet, że musi być skompilowana w ogóle, może równie dobrze zostać zinterpretowana. –

+0

@ JörgWMittag Masz swój punkt :). Ale nadal pozostaje pytanie, jak to działa. –

Odpowiedz

0

myślę @JBNizet zorientowali się rozwiązanie już w komentarzach (i moje przypuszczenie okazało się błędne). Ponieważ jednak nie po to jako odpowiedź, zrobię go:

Sposób main nie powinien wykazywać żadnych dynamiczne zachowanie, ponieważ jest zawsze kiedyś się nazwać jedenpojedyncza metodaTestRide.ride(Cycle c). Nie ma innej możliwej metody, więc nie ma nic do odkrycia.

Dynamiczne wywołanie metody znajduje się w tej metodzie TestRide.ride(Cycle c). A teraz, kiedy opublikowałeś ten kod, rzeczywiście widzimy dynamiczną wysyłkę metod za pomocą invokevirtual. W końcu nie ma niespodzianek. Jest tam wysyłanie dynamicznej metody.

1

Po raz pierwszy jest dla Javy 8 lambd i nie-kodu Java, dzięki czemu można o tym zapomnieć.

Oprócz tego, istnieją cztery instrukcje Invoke (invokespecial, invokestatic, invokevirtual i invokeinterface). Możesz zobaczyć dokładną semantykę w sepcifikacji JVM, ale podstawową cechą jest to, że wywołania metody są wykonywane w sposób invokevirtual i invokeinterface, tj. Rzeczywista metoda nazywana jest wybierana w czasie wykonywania w oparciu o typ conrete typu targetu.

Jedyne połączenie wirtualne w kodzie znajduje się w pliku TestRide.ride. Wyszczególniony cel to Cycle.ride:()V. Ponieważ jednak jest to wywołanie wirtualne, JVM sprawdzi rzeczywisty typ pierwszego argumentu w czasie wykonywania i wywoła najbardziej pochodną wersję tej metody.

Jest to podobne do wywołań metod wirtualnych w C++, z tym że abstrakcja kompilacji JVM i JIT pozwala na bardziej zoptymalizowane implementacje.

Należy również zauważyć, że nie należy mylić tego z przeciążaniem metod, które jest formą polimorfizmu kompilacji. W przypadku przeciążonych metod kompilator wybiera, który z nich wywoła na podstawie typu czasu kompilacji argumentów.