2017-02-14 32 views
5

Tytuł może wprowadzać w błąd, ale jako osoba nienatywna nie mógł wymyślić lepszego.Typ klasy odniesienia i rzeczywisty typ klasy, która decyduje o wyborze metody?

że mam dwie klasy, Dog i Fox:

public class Dog { 
    public String bark() { 
     return "Wuff"; 
    } 
    public String play(Dog d) { 
     return "Wuff" + d.bark(); 
    } 
} 


public class Fox extends Dog { 
    public String bark() { 
     return "Ringding" ; 
    } 
    public String play(Fox f) { 
     return "Ringding" + f.bark(); 
    } 
} 

A ja stworzyć kilka instancji, jak również wywołać pewne metody

Fox foxi = new Fox(); 
Dog hybrid = new Fox(); 
System.out.println(hybrid.play(foxi)); // Output number 1 
System.out.println(foxi.play(hybrid)); // Output number 2 

Dla 1. Ouput Spodziewałem "RingdingRingding" bo rzeczywiście jest hybrid odwołanie do instancji modelu Dog, nawet jeśli odwołanie ma typ: Dog to nadal dotyczy s do obiektu Fox, ale nadal mam ten wynik:

WuffRingding

Drugi mam ten sam problem, ponieważ foxi jest instancją Fox i hybrid jest faktycznie instancja Fox (? nie wiem co odniesienia, prawy), przy czym powinny być "RingdingRingding" ouput ale potem znowu, mam:

WuffRingding

Czy ktoś może wyjaśnić dlaczego?

+1

Istotne jest to, że 'Fox.play (Fox)' nie zastępuje 'Dog.play (Dog)'. Ponieważ typ 'hybrid' to' Dog', wywoływane jest przeciążenie 'play (Dog)'. –

+0

@AndyTurner cześć Andy, dziękuję za odpowiedź. Nadal, jeśli chodzi o typowanie, typ siłowy ma znaczenie? Powiedzmy, że rzuciłem (Dog) na instancję Foxa i zadzwonię do metody kory, nadal będzie to metoda w klasie Fox, prawda? Jestem trochę zdezorientowany między odniesieniem a rzutowaniem .. –

+1

Metoda do wywołania jest określana w czasie kompilacji, a nie w czasie wykonywania. Tak więc, jeśli podasz odniesienie do "psa" do metody, wywoływane jest przeciążenie polegające na wywołaniu "psa" - nawet jeśli odnosi się to do 'Foxa' w czasie wykonywania. –

Odpowiedz

2

Dwie ważne rzeczy do wywołania metod.

Masz dwa razy: czas kompilacji i czas wykonywania.
Zasady nie są takie same między tymi dwoma razy.

  • w czasie kompilacji kompilator musi określić statycznie, jaki dokładny podpis metody jest nazywany, aby skompilować dobrze.
    To powiązanie jest statyczne, ponieważ kompilator nie ma wpływu na konkretną instancję, w której wywoływana jest metoda i to samo dotyczy parametrów przekazywanych do metody.
    Kompilator nie polega na typach efektywnych, ponieważ w środowisku wykonawczym typy efektywne mogą ulec zmianie w trakcie wykonywania.
    Tak więc kompilator przeszukuje dostępne metody dla zadeklarowanego typu, który jest bardziej szczegółową metodą zgodną z zadeklarowanymi typami parametrów przekazanych do tego typu.

  • w środowisku wykonawczym metoda instancji z klasy lub z innej będzie używana zgodnie z efektywną instancją, w której wywołana jest metoda.
    Jednak metoda wywoływana musi być zgodna z sygnaturą określoną podczas kompilacji.

1) W pierwszym przypadku:

Fox foxi = new Fox(); 
Dog hybrid = new Fox(); 
System.out.println(hybrid.play(foxi)); // Output number 1 
  • pierwsze (czas COMPILE)

Na przykład kotów, kompilator musi znaleźć najbardziej odpowiednią metodę play() jako parametr zmienna z zadeklarowanym typem Fox.

W klasie Dog, pojedyncza metoda play() z podpisem zgodnym istnieje:

public String play(Dog d) { 

Więc podpis ten jest używany do wiązania: String play(Dog d).

O metodzie bark() jest to o wiele bardziej oczywiste, ponieważ istnieje tylko jeden podpis metody bark().
Więc nie mamy niejednoznaczność w sprawie sposobu, który jest związany w czasie kompilacji

  • Drugi raz (wykonawczego):

przy starcie metoda betonowej przykład String play(Dog d) jest wywoływany. Zmienna hybrid odnosi się do instancji Foxa, ale Fox nie zastępuje String play(Dog d). Fox definiuje metodę play(), ale z innym podpisem:

public String play(Fox f) { 

Więc JVM wywołuje metodę psa public String play(Dog d) {.
Następnie wywołuje metodę efektywnego typu d po wykonaniu d.bark(), a d odnosi się do instancji .

Wypisuje "WuffRingding".


2) W drugim przypadku:

Fox foxi = new Fox(); 
Dog hybrid = new Fox(); 
System.out.println(foxi.play(hybrid)); // Output number 2 
  • pierwszy raz (kompilacji)

Na przykład Fox kompilator musi znaleźć najbardziej odpowiednią metodę play() jako parametr zmienna o zadeklarowanym typie Dog.

W Fox klasie, dwie play() metody z parametrem zgodnym istnieje:

public String play(Dog d) { // inherited from the parent class 

public String play(Fox f) { // declared in Fox 

Kompilator musi wybrać bardziej konkretny sposób kontekście wywołania metody
identyfikuje sposób bardziej szczegółowy, że drugi dla zadeklarowanego parametru typu: public String play(Dog d). Kompilator wiąże wywołanie metody play() z public String play(Dog d) podczas kompilowania klasy.

  • Drugi raz (czas pracy):

W czasie wykonywania metoda konkretnej instancji String play(Dog d) jest wywoływany.
Jeśli chodzi o pierwszy przypadek, zmienna foxi odnosi się do instancji Fox, ale Fox nie zastępuje String play(Dog d).
Tak więc JVM wywołuje metodę Dog od public String play(Dog d).
Następnie wywołuje metodę efektywnego typu f, gdy wykonywana jest f.bark(), a f odnosi się do instancji Fox.

"WuffRingding" ponownie wyświetla się.


Aby uniknąć tego rodzaju zaskoczenia należy dodać @Override w metodach mających zastąpić metody klasy dominującej:
na przykład:

@Override 
public String play(Fox f) { 
    return "Ringding" + f.bark(); 
} 

Jeśli metoda nie skutecznie zastępują play(Fox f) metoda w hierarchii, kompilator narzeka na to.

+0

Wow, dziękuję za odpowiedź. Wciąż próbowałem coś takiego: Dog a = (Dog) foxi; a.bark(); Dane wyjściowe to nadal "Dzwonienie", nawet jeśli "a" jest referencją typu "Pies", czy możesz wyjaśnić, dlaczego? –

+1

Zapraszamy! Ponownie przeczytaj tę część mojej odpowiedzi: "O metodzie bark() jest to bardzo oczywiste, ponieważ istnieje tylko jeden podpis metody bark() Więc nie mamy żadnych wątpliwości co do metody, która jest związana z czas kompilacji ". Oznacza to, że w czasie kompilacji unikalna metoda "bark()" była związana w klasie skompilowanej. A w czasie wykonywania JVM używa metody związanej ('bark()') efektywnej instancji, na której metoda jest wywoływana. – davidxxx

+0

Mam to teraz, wielkie dzięki! –

1

Tutaj w twoim przypadku Metoda play jest przeciążona, nie zastępowana.

Po wykonaniu tej Dog d = new Fox() Odniesienie Dog wywoła metodę klasy Dog tylko do czasu, gdy metody Dog klasy są zastępowane przez Fox klasie. Gdy metody są nadpisywane, wywoływanie takich metod jest rozwiązywane w środowisku wykonawczym, ale wywoływanie przeciążonych metod jest rozwiązywane w czasie kompilacji.

Odczytywanie polimorfizmu statycznego i polimorfizmu wykonawczego w celu uzyskania dalszego luzu.

1

Wydaje się, że rzeczą, która powoduje, że zamieszanie jest to, że myślisz play-metody w podklasie Foxzadajnikami play-metody nadklasy, podczas gdy faktycznie tylko przeciążenia go.

Jeśli zmienisz typ parametru f w metodzie odtwarzania klasy Fox na typ Dog, wynik wyjściowy będzie "RingdingRingding" dwa razy z powodów, które analizowałeś w pytaniu, ponieważ w tym przypadku gra -metod poprawnie zastępuje metodę nadklasy.

Spójrzmy na sprawy z play-przeciążonych metod bardziej szczegółowo:

hybrid.play(foxi) 

Kompilator patrzy deklarowanej statycznego typu hybrid która Dog. foxi jest zadeklarowany jako Fox.Dlatego kompilator szuka metody odtwarzania w klasie Dog, która oczekuje jednego parametru typu statycznego: Fox. Nie powiodło się, ponieważ istnieje tylko metoda odtwarzania, która oczekuje jednego parametru typu statycznego: Dog. Jednak kompilator nadal będzie wybierał tę metodę do wywołania w końcu, ponieważ Dog jest nadklasą Fox.

foxi.play(hybrid) 

Kompilator patrzy deklarowanej statycznego typu foxi która Fox. hybrid jest zadeklarowany jako Dog. Dlatego kompilator szuka metody odtwarzania w klasie Fox, która oczekuje jednego parametru typu statycznego: Dog. Istnieją dwie metody odtwarzania w klasie Fox kompilator może wybierać spośród: play(Fox f) i odziedziczonej metody przeładowanej play(Dog d). play(Fox f) nie jest prawidłowym wyborem, ponieważ ta metoda oczekuje jednego parametru typu Fox, a hybrid jest zadeklarowany jako Dog. Oznacza to, że kompilator ponownie wybierze metodę play(Dog d), która jest zadeklarowana w klasie Dog, podobnie jak w przypadku poprzedniej instrukcji.

Powodem dlaczego kompilator nie pozwalają przesłonić play(Fox f) z play(Dog d) jest następujący: Wyobraźmy sobie, że jest ono dozwolone i ktoś by to zrobić:

Dog doggo = new Dog(); 
hybrid.play(doggo); 

Teraz nadpisane play(Fox f) metoda będzie wywoływana z parametr wejściowy typu Dog w czasie działania, który nie działa, ponieważ implementacja play(Fox f) oczekuje, że f jest nie tylko Dog, ale bardziej wyspecjalizowanym Fox.

Aby uniknąć przeciążenia/pomijania pomyłek, należy zanotować metody, które powinny zastąpić metody nadklasy za pomocą @Override. Kompilator nie będzie chciał skompilować kodu, jeśli metoda z adnotacją @Override faktycznie nie zastępuje metody nadklasy.

1

Zasady określania, która metoda zostanie wywołana, to pretty complicated, ale spróbuję podsumować je dla tych przypadków tutaj.

Po pierwsze, dla hybrid.play(foxi):

  1. Określić klasy lub interfejs do wyszukiwania

    hybrid ma typ Dog, więc interfejs Dog będzie szukał metod. Oznacza to, że nawet metody, które zdefiniowałeś na Dog, będą prawdopodobnie wywoływane. Są to:

    bark() 
    play(Dog) 
    
  2. Ustal Method Podpis

    Jesteś wywołanie metody play, z parametrem typu Fox. Fox jest podtypem Dog, więc metoda play(Dog) jest dopasowana.

Ta metoda została wywołana.

Następnie dla foxi.play(hybrid):

  1. Określić klasy lub interfejs do wyszukiwania

    foxi ma typ Fox, więc interfejs Fox będzie szukał metod. Dostępne są następujące metody:

    bark() 
    play(Dog) 
    play(Fox) 
    

    Uwaga, play(Fox) nie zastępują play(Dog): nie mają one ten sam podpis metody, więc play(Fox) jest jedynie przeciążenie.

  2. Ustal Method Podpis

    Jesteś wywołanie metody play, z parametrem typu Dog. Z tego powodu wywoływana jest metoda play(Dog), ponieważ jest to jedyna, która pasuje.

    Nie ma znaczenia, że ​​hybrid ma środowisko wykonawcze typu Fox: ten wybór metody, która ma zostać wywołana, ma miejsce podczas kompilacji.

    Dlatego wywoływany jest play(Dog), a nie play(Fox).