Wczoraj odbyłem dwugodzinny telefoniczny wywiad techniczny (który zdałem, woohoo!), Ale całkowicie stłumiłem następujące pytanie dotyczące dynamicznego wiązania w Javie. Jest to podwójnie zagadkowe, ponieważ uczyłem tego pojęcia studentom, kiedy byłem asystentem kilka lat temu, więc perspektywa, którą podałem im dezinformację, jest trochę niepokojąca ...
Oto problem, który mi dano:
/* What is the output of the following program? */
public class Test {
public boolean equals( Test other ) {
System.out.println( "Inside of Test.equals" );
return false;
}
public static void main( String [] args ) {
Object t1 = new Test();
Object t2 = new Test();
Test t3 = new Test();
Object o1 = new Object();
int count = 0;
System.out.println( count++ );// prints 0
t1.equals( t2 ) ;
System.out.println( count++ );// prints 1
t1.equals( t3 );
System.out.println( count++ );// prints 2
t3.equals( o1 );
System.out.println( count++ );// prints 3
t3.equals(t3);
System.out.println( count++ );// prints 4
t3.equals(t2);
}
}
Zapewniłem, że wynikiem powinny być dwie oddzielne instrukcje print z nadpisanej equals()
metody: at t1.equals(t3)
i t3.equals(t3)
. Ten drugi przypadek jest wystarczająco oczywisty, a w pierwszym przypadku, mimo że t1
ma odwołanie do typu Object, jest on tworzony jako test typu, więc dynamiczne wiązanie powinno wywołać przesłoniętą formę metody.
Najwyraźniej nie. Mój ankieter zachęcił mnie, abym sam uruchomił program, a oto wynik z nadpisanej metody był tylko jeden: na linii t3.equals(t3)
.
Moje pytanie brzmi więc, dlaczego? Jak już wspomniałem, mimo że t1
jest referencją typu Object (więc statyczne wiązanie wywołałoby equals()
metodę Object ), dynamiczne wiązanie powinno zadbać o wywołanie najbardziej szczegółowej wersji metody opartej na instancji typu odwołania. czego mi brakuje?
źródło
Odpowiedzi:
Java używa statycznego wiązania dla przeciążonych metod i dynamicznego wiązania dla zastąpionych. W naszym przykładzie metoda equals jest przeciążona (ma inny typ parametru niż Object.equals ()), więc wywołana metoda jest powiązana z typem referencyjnym w czasie kompilacji.
Tutaj jest dyskusja
Fakt, że jest to metoda równości, nie ma większego znaczenia, poza tym, że częstym błędem jest przeciążanie zamiast jej zastępowania, o czym już wiesz, opierając się na swojej odpowiedzi na problem w wywiadzie.
Edycja: tutaj również dobry opis . Ten przykład pokazuje podobny problem związany z typem parametru, ale spowodowany przez ten sam problem.
Uważam, że gdyby powiązanie było faktycznie dynamiczne, to każdy przypadek, w którym obiekt wywołujący i parametr były wystąpieniem Test, spowodowałby wywołanie metody przesłoniętej. Zatem t3.equals (o1) byłby jedynym przypadkiem, który nie zostałby wydrukowany.
źródło
equals
MetodaTest
nie przesłonićequals
metodęjava.lang.Object
. Spójrz na typ parametru!Test
Klasa przeciążeniaequals
ze sposobem, który akceptujeTest
.Jeśli
equals
metoda ma zastąpić, należy użyć adnotacji @Override. Spowodowałoby to błąd kompilacji, który wskazywałby na ten powszechny błąd.źródło
Co ciekawe, w kodzie Groovy (który można skompilować do pliku klasy), wszystkie wywołania oprócz jednego wykonywałyby instrukcję print. (Ten, który porównuje Test z Object, najwyraźniej nie wywoła funkcji Test.equals (Test).) Dzieje się tak, ponieważ groovy WYKONUJE całkowicie dynamiczne typowanie. Jest to szczególnie interesujące, ponieważ nie ma żadnych zmiennych, które są jawnie wpisywane dynamicznie. Czytałem w kilku miejscach, że jest to uważane za szkodliwe, ponieważ programiści oczekują, że groovy zrobi coś w Javie.
źródło
Java nie obsługuje współwariancji parametrów, tylko w typach zwracanych.
Innymi słowy, podczas gdy typ zwracany w metodzie przesłaniającej może być podtypem tego, co znajdował się w przesłoniętej metodzie, nie jest to prawdą w przypadku parametrów.
Jeśli parametr dla równości w Object to Object, umieszczenie równości z czymkolwiek innym w podklasie będzie przeciążoną, a nie przesłoniętą metodą. Stąd jedyną sytuacją, w której ta metoda zostanie wywołana, jest sytuacja, gdy statycznym typem parametru jest Test, jak w przypadku T3.
Powodzenia w procesie rozmowy kwalifikacyjnej! Chciałbym odbyć rozmowę kwalifikacyjną w firmie, która zadaje tego typu pytania zamiast zwykłych pytań dotyczących algo / struktur danych, których uczę moich studentów.
źródło
Myślę, że klucz polega na tym, że metoda equals () nie jest zgodna ze standardem: pobiera inny obiekt Test, a nie obiekt Object, a zatem nie przesłania metody equals (). Oznacza to, że w rzeczywistości przeciążałeś go tylko, aby zrobić coś specjalnego, gdy otrzyma obiekt Test, dając mu obiekt Object wywołuje Object.equals (Object o). Przeglądanie tego kodu za pomocą dowolnego środowiska IDE powinno pokazać dwie metody equals () dla testu.
źródło
Metoda jest przeciążona zamiast zastępowania. Equals zawsze przyjmuje Object jako parametr.
btw, masz przedmiot na ten temat w efektywnej Javie Blocha (który powinieneś posiadać).
źródło
Kilka uwag w Dynamic Binding (DD) i Static Binding̣̣̣ (SB) po pewnym czasie wyszukiwania:
1. wykonanie pomiaru czasu : (1)
2. używany do :
Odniesienie:
źródło
Jeśli zostanie dodana inna metoda, która zastępuje zamiast przeciążania, wyjaśni wywołanie dynamicznego wiązania w czasie wykonywania.
/ * Jaki jest wynik następującego programu? * /
public class DynamicBinding { public boolean equals(Test other) { System.out.println("Inside of Test.equals"); return false; } @Override public boolean equals(Object other) { System.out.println("Inside @override: this is dynamic binding"); return false; } public static void main(String[] args) { Object t1 = new Test(); Object t2 = new Test(); Test t3 = new Test(); Object o1 = new Object(); int count = 0; System.out.println(count++);// prints 0 t1.equals(t2); System.out.println(count++);// prints 1 t1.equals(t3); System.out.println(count++);// prints 2 t3.equals(o1); System.out.println(count++);// prints 3 t3.equals(t3); System.out.println(count++);// prints 4 t3.equals(t2); } }
źródło
Znalazłem interesujący artykuł o wiązaniu dynamicznym vs. statycznym. Zawiera fragment kodu do symulacji dynamicznego wiązania. Dzięki temu mój kod stał się bardziej czytelny.
https://sites.google.com/site/jeffhartkopf/covariance
źródło
Odpowiedź na pytanie „dlaczego?” tak właśnie definiuje się język Java.
Cytując artykuł z Wikipedii dotyczący kowariancji i kontrawariancji :
Inne języki są różne.
źródło
Jest bardzo jasne, że nie ma tutaj koncepcji nadpisywania. Jest to przeciążenie metod.
Object()
sposób klasy obiekt bierze parametru odniesienia do typu obiekt i tymequal()
sposobie, parametr odniesienia typu testu.źródło
Spróbuję to wyjaśnić na dwóch przykładach, które są rozszerzonymi wersjami niektórych przykładów, które znalazłem w Internecie.
public class Test { public boolean equals(Test other) { System.out.println("Inside of Test.equals"); return false; } @Override public boolean equals(Object other) { System.out.println("Inside of Test.equals ot type Object"); return false; } public static void main(String[] args) { Object t1 = new Test(); Object t2 = new Test(); Test t3 = new Test(); Object o1 = new Object(); int count = 0; System.out.println(count++); // prints 0 o1.equals(t2); System.out.println("\n" + count++); // prints 1 o1.equals(t3); System.out.println("\n" + count++);// prints 2 t1.equals(t2); System.out.println("\n" + count++);// prints 3 t1.equals(t3); System.out.println("\n" + count++);// prints 4 t3.equals(o1); System.out.println("\n" + count++);// prints 5 t3.equals(t3); System.out.println("\n" + count++);// prints 6 t3.equals(t2); } }
Tutaj, dla wierszy z wartościami zliczania 0, 1, 2 i 3; mamy odniesienie do Object dla o1 i t1 w
equals()
metodzie. Dlatego w czasie kompilacjiequals()
metoda z pliku Object.class zostanie ograniczona.Jednak, mimo odniesienia od t1 jest obiekt , ma intialization z klasy test .
Object t1 = new Test();
.Dlatego w czasie wykonywania wywołuje
public boolean equals(Object other)
metodę, która jest.
Teraz, dla zliczania wartości 4 i 6, jest znowu proste, że t3, który ma odniesienie i inicjalizację Test, wywołuje
equals()
metodę z parametrem jako odwołania do obiektu i jestOK!
źródło