Dynamiczne wiązanie Java i przesłanianie metod

90

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 t1ma 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 t1jest 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?

Magsol
źródło
Uprzejmie proszę znaleźć mój post z tą odpowiedzią, w którym starałem się wyjaśnić dodatkowe przypadki. Byłbym bardzo wdzięczny za wkład :)
Devendra Lattu

Odpowiedzi:

82

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.

Rudzik
źródło
Wiele osób zwraca uwagę, że jest przeciążony i nie jest nadpisywany, ale nawet z tym można by się spodziewać, że poprawnie rozwiązuje przeciążony. Twój post jest właściwie jedynym jak dotąd, który poprawnie odpowiada na to pytanie, o ile wiem.
Bill K
4
Mój błąd polegał na tym, że całkowicie przeoczyłem fakt, że metoda jest rzeczywiście przeciążona, a nie zastąpiona. Widziałem „równa się ()” i od razu pomyślałem, że odziedziczyłem i zastąpiono. Wygląda na to, że po raz kolejny poprawiłem szerszą i trudniejszą koncepcję, ale schrzaniłem proste szczegóły. : P
Magsol
14
inny powód, dla którego istnieje adnotacja @Override.
Matt
1
Powtórz za mną: „Java używa statycznego wiązania dla przeciążonych metod i dynamicznego wiązania dla zastąpionych” - +1
Mr_and_Mrs_D
1
więc ukończyłem szkołę, nie wiedząc o tym. Dzięki!
Atieh
26

equalsMetoda Testnie przesłonić equalsmetodę java.lang.Object. Spójrz na typ parametru! TestKlasa przeciążenia equalsze sposobem, który akceptuje Test.

Jeśli equalsmetoda ma zastąpić, należy użyć adnotacji @Override. Spowodowałoby to błąd kompilacji, który wskazywałby na ten powszechny błąd.

erickson
źródło
Tak, nie jestem do końca pewien, dlaczego przegapiłem ten prosty, ale kluczowy szczegół, ale dokładnie na tym polegał mój problem. Dziękuję Ci!
Magsol
+1 za prawdziwą odpowiedź na ciekawe wyniki pytającego
Matt b
Uprzejmie proszę znaleźć mój post z tą odpowiedzią, w którym starałem się wyjaśnić dodatkowe przypadki. Byłbym bardzo wdzięczny za wkład :)
Devendra Lattu
7

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.

Benson
źródło
1
Niestety cena, jaką Groovy płaci za to, jest ogromnym hitem wydajnościowym, ponieważ każde wywołanie metody wykorzystuje odbicie. Oczekiwanie, że jeden język będzie działał dokładnie tak samo, jak inny, jest ogólnie uważane za szkodliwe. Trzeba być świadomym różnic.
Joachim Sauer
Powinno być ładne i szybkie z invokedynamic w JDK7 (a nawet przy użyciu podobnej techniki implementacji dzisiaj).
Tom Hawtin - tackline
5

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.

Uri
źródło
1
Masz na myśli kontrawariantne parametry.
Tom Hawtin - tackline
W jakiś sposób całkowicie zatuszowałem fakt, że różne parametry metody wewnętrznie tworzą metodę przeciążoną, a nie nadpisaną. Och, nie martw się, były też pytania o algo / struktury danych. : P I dziękuję za szczęście, będę go potrzebować! :)
Magsol
4

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.

P Arrayah
źródło
To i większość odpowiedzi nie mają sensu. Problem nie polega na tym, że zamiast nadpisywania używane jest przeciążanie. To dlatego nie jest przeciążona metoda używana dla t1.equals (t3), gdy t1 jest zadeklarowane jako Object, ale zainicjowane do Test.
Robin
4

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ć).

Gilles
źródło
Efektywna Java Joshua Blocha?
DJClayworth
Skuteczne tak, myślałem o czymś innym podczas pisania: D
Gilles,
4

Kilka uwag w Dynamic Binding (DD) i Static Binding̣̣̣ (SB) po pewnym czasie wyszukiwania:

1. wykonanie pomiaru czasu : (1)

  • DB: w czasie wykonywania
  • SB: czas kompilacji

2. używany do :

  • DB: nadrzędny
  • SB: przeciążenie (statyczne, prywatne, końcowe) (Ref.2)

Odniesienie:

  1. Wykonaj średni przelicznik, której metody preferujesz
  2. Ponieważ nie można przesłonić metody za pomocą modyfikatora static, private lub final
  3. http://javarevisited.blogspot.com/2012/03/what-is-static-and-dynamic-binding-in.html
NguyenDat
źródło
2

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);
    }
}
Prabu R
źródło
0

Odpowiedź na pytanie „dlaczego?” tak właśnie definiuje się język Java.

Cytując artykuł z Wikipedii dotyczący kowariancji i kontrawariancji :

Kowariancja typu zwracanego jest zaimplementowana w języku programowania Java w wersji J2SE 5.0. Typy parametrów muszą być dokładnie takie same (niezmienne) w przypadku zastępowania metody, w przeciwnym razie metoda zostanie przeciążona definicją równoległą.

Inne języki są różne.

ykaganovich
źródło
Mój problem był mniej więcej taki sam, jak widzenie 3 + 3 i pisanie 9, potem oglądanie 1 + 1 i pisanie 2. Rozumiem, jak definiowany jest język Java; w tym przypadku z jakiegoś powodu całkowicie pomyliłem metodę z czymś, czym nie była, mimo że uniknąłem tego błędu w innym miejscu tego samego problemu.
Magsol
0

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 tym equal()sposobie, parametr odniesienia typu testu.

ankush gatfane
źródło
-1

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 kompilacji equals()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

zastąpiona metoda

. wprowadź opis obrazu tutaj

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 jest

przeciążona metoda

OK!

Ponownie, aby lepiej zrozumieć, jaką metodę wywoła kompilator, po prostu kliknij metodę, a Eclipse podświetli metody podobnych typów, które według niego będą wywoływane w czasie kompilacji. Jeśli nie zostanie wywołany w czasie kompilacji, te metody są przykładem zastępowania metod.

wprowadź opis obrazu tutaj

Devendra Lattu
źródło