Java Dynamic Binding und Methodenüberschreibung


89

Gestern hatte ich ein zweistündiges technisches Telefoninterview (das ich bestanden habe, woohoo!), Aber ich habe die folgende Frage bezüglich der dynamischen Bindung in Java vollständig beantwortet. Und es ist doppelt rätselhaft, weil ich Studenten vor einigen Jahren als TA dieses Konzept beigebracht habe. Die Aussicht, dass ich ihnen Fehlinformationen gegeben habe, ist also etwas beunruhigend ...

Hier ist das Problem, das mir gegeben wurde:

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

Ich habe behauptet, dass die Ausgabe zwei separate Druckanweisungen innerhalb der überschriebenen equals()Methode sein sollte: at t1.equals(t3)und t3.equals(t3). Der letztere Fall ist offensichtlich genug, und im ersteren Fall wird er, obwohl er t1eine Referenz vom Typ Object hat, als Typ Test instanziiert, sodass die dynamische Bindung die überschriebene Form der Methode aufrufen sollte.

Scheinbar nicht. Mein Interviewer ermutigte mich, das Programm selbst auszuführen, und siehe da, es gab nur eine einzige Ausgabe der überschriebenen Methode: an der Leitung t3.equals(t3).

Meine Frage ist dann, warum? Wie bereits erwähnt, sollte die dynamische Bindung dafür sorgen, dass die spezifischste Version der Methode basierend auf dem instanziierten Typ der Referenz aufgerufen wird, obwohl t1es sich um eine Referenz vom Typ Object handelt (statische Bindung würde also die equals()Methode von Object aufrufen ) . Was vermisse ich?


Bitte finden Sie meinen Beitrag zu dieser Antwort, in dem ich mein Bestes gegeben habe, um ihn mit weiteren Fällen zu erklären. Ich würde mich sehr über Ihre
Beiträge freuen

Antworten:


81

Java verwendet statische Bindung für überladene Methoden und dynamische Bindung für überschriebene. In Ihrem Beispiel ist die Methode equals überladen (hat einen anderen Parametertyp als Object.equals ()), sodass die aufgerufene Methode zur Kompilierungszeit an den Referenztyp gebunden ist .

Einige Diskussionen hier

Die Tatsache, dass es sich um die Equals-Methode handelt, ist nicht wirklich relevant, abgesehen davon, dass es ein häufiger Fehler ist, sie zu überladen, anstatt sie zu überschreiben, was Ihnen bereits aufgrund Ihrer Antwort auf das Problem im Interview bekannt ist.

Edit: Eine gute Beschreibung auch hier . Dieses Beispiel zeigt ein ähnliches Problem im Zusammenhang mit dem Parametertyp, das jedoch durch dasselbe Problem verursacht wird.

Ich glaube, wenn die Bindung tatsächlich dynamisch wäre, würde jeder Fall, in dem der Aufrufer und der Parameter eine Instanz von Test wären, dazu führen, dass die überschriebene Methode aufgerufen wird. T3.equals (o1) wäre also der einzige Fall, der nicht gedruckt würde.


Viele Leute weisen darauf hin, dass es überladen und nicht überschrieben ist, aber selbst damit würde man erwarten, dass es das überladene korrekt auflöst. Ihr Beitrag ist tatsächlich der einzige, der die Frage soweit richtig beantwortet, soweit ich das beurteilen kann.
Bill K

4
Meinem Fehler fehlte völlig die Tatsache, dass die Methode tatsächlich überladen und nicht überschrieben wird. Ich sah "equals ()" und dachte sofort, ererbt und überschrieben. Sieht so aus, als hätte ich das umfassendere und schwierigere Konzept wieder richtig verstanden, aber die einfachen Details vermasselt. : P
Magsol

14
Ein weiterer Grund, warum die Annotation @Override vorhanden ist.
Matt

1
Wiederholen Sie nach mir: "Java verwendet statische Bindung für überladene Methoden und dynamische Bindung für überschriebene" - +1
Mr_and_Mrs_D

1
Also habe ich meinen Abschluss gemacht, ohne das zu wissen. Vielen Dank!
Atieh

25

Die equalsMethode von Testüberschreibt die equalsMethode von nicht java.lang.Object. Schauen Sie sich den Parametertyp an! Die TestKlasse wird equalsmit einer Methode überladen , die a akzeptiert Test.

Wenn die equalsMethode überschrieben werden soll, sollte die Annotation @Override verwendet werden. Dies würde einen Kompilierungsfehler verursachen, der auf diesen häufigen Fehler hinweist.


Ja, ich bin mir nicht ganz sicher, warum ich dieses einfache, aber entscheidende Detail übersehen habe, aber genau dort war mein Problem. Danke dir!
Magsol

+1 für die wahre Antwort auf die merkwürdigen Ergebnisse des Fragestellers
matt b

Bitte finden Sie meinen Beitrag zu dieser Antwort, in dem ich mein Bestes gegeben habe, um ihn mit weiteren Fällen zu erklären. Ich würde mich sehr über Ihre
Beiträge freuen

6

Interessanterweise würden in Groovy-Code (der zu einer Klassendatei kompiliert werden könnte) alle Aufrufe bis auf einen die print-Anweisung ausführen. (Derjenige, der einen Test mit einem Objekt vergleicht, ruft die Test.equals (Test) -Funktion eindeutig nicht auf.) Dies liegt daran, dass groovy DOES eine vollständig dynamische Typisierung ausführt. Dies ist besonders interessant, da es keine Variablen gibt, die explizit dynamisch typisiert werden. Ich habe an einigen Stellen gelesen, dass dies als schädlich angesehen wird, da Programmierer erwarten, dass Groovy das Java-Ding macht.


1
Leider ist der Preis, den Groovy dafür zahlt, ein massiver Leistungseinbruch, da jeder Methodenaufruf Reflexion verwendet. Die Erwartung, dass eine Sprache genauso funktioniert wie eine andere, wird im Allgemeinen als schädlich angesehen. Man muss sich der Unterschiede bewusst sein.
Joachim Sauer

Sollte mit invokedynamic in JDK7 nett und schnell sein (oder heute sogar eine ähnliche Implementierungstechnik verwenden).
Tom Hawtin - Tackline

5

Java unterstützt keine Ko-Varianz bei Parametern, sondern nur bei Rückgabetypen.

Mit anderen Worten, während Ihr Rückgabetyp in einer überschreibenden Methode möglicherweise ein Untertyp dessen ist, was er in der überschriebenen Methode war, gilt dies nicht für Parameter.

Wenn Ihr Parameter für Gleichheit in Objekt Objekt ist, ist das Einfügen einer Gleichheit mit etwas anderem in einer Unterklasse eine überladene und keine überschriebene Methode. Daher ist die einzige Situation, in der diese Methode aufgerufen wird, wenn der statische Typ des Parameters Test ist, wie im Fall von T3.

Viel Glück beim Bewerbungsgespräch! Ich würde gerne in einem Unternehmen interviewt werden, das diese Art von Fragen anstelle der üblichen Fragen zu Algo / Datenstrukturen stellt, die ich meinen Schülern beibringe.


1
Sie meinen kontravariante Parameter.
Tom Hawtin - Tackline

Ich habe irgendwie die Tatsache völlig beschönigt, dass verschiedene Methodenparameter eine überladene Methode erzeugen, keine überschriebene. Oh, keine Sorge, es gab auch Fragen zu Algo / Datenstrukturen. : P Und danke für das Glück, ich werde es brauchen! :)
Magsol

4

Ich denke, der Schlüssel liegt in der Tatsache, dass die Methode equals () nicht dem Standard entspricht: Sie nimmt ein anderes Testobjekt auf, kein Objektobjekt, und überschreibt daher die Methode equals () nicht. Dies bedeutet, dass Sie es tatsächlich nur überladen haben, um etwas Besonderes zu tun, wenn es ein Testobjekt erhält, während es Objektobjektaufrufe Object.equals (Object o) erhält. Wenn Sie diesen Code durch eine IDE durchsuchen, sollten Sie zwei equals () -Methoden für den Test anzeigen.


Dies und die meisten Antworten verfehlen den Punkt. Es geht nicht um die Tatsache, dass Überladen verwendet wird, anstatt es zu überschreiben. Aus diesem Grund wird die überladene Methode nicht für t1.equals (t3) verwendet, wenn t1 als Objekt deklariert, aber auf Test initialisiert wird.
Robin

4

Die Methode wird überladen anstatt überschrieben. Gleiche nehmen immer ein Objekt als Parameter.

Übrigens haben Sie einen Gegenstand dazu in Blochs effektivem Java (den Sie besitzen sollten).


Joshua Blochs effektives Java?
DJClayworth

Effektiv ja, dachte beim Tippen an etwas anderes: D
Gilles

4

Einige Hinweise in Dynamic Binding (DD) und Static Binding̣̣̣ (SB) nach einer Weile der Suche:

1.Timing ausführen : (Ref.1)

  • DB: zur Laufzeit
  • SB: Compilerzeit

2. Verwendet für :

  • DB: überschreiben
  • SB: Überladung (statisch, privat, endgültig) (Ref.2)

Referenz:

  1. Führen Sie den mittleren Resolver aus, den Sie bevorzugen
  2. Da kann Methode mit Modifikator statisch, privat oder endgültig nicht überschrieben werden
  3. http://javarevisited.blogspot.com/2012/03/what-is-static-and-dynamic-binding-in.html

2

Wenn eine andere Methode hinzugefügt wird, die überschreibt, anstatt zu überladen, wird der dynamische Bindungsaufruf zur Laufzeit erläutert.

/ * Was ist die Ausgabe des folgenden Programms? * /

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


0

Die Antwort auf die Frage "Warum?" So wird die Java-Sprache definiert.

Um den Wikipedia-Artikel über Kovarianz und Kontravarianz zu zitieren :

Die Kovarianz vom Rückgabetyp ist in der Java-Programmiersprache J2SE 5.0 implementiert. Parametertypen müssen für das Überschreiben von Methoden genau gleich (invariant) sein, andernfalls wird die Methode stattdessen mit einer parallelen Definition überladen.

Andere Sprachen sind anders.


Mein Problem war ungefähr gleichbedeutend damit, 3 + 3 zu sehen und 9 zu schreiben, dann 1 + 1 zu sehen und 2 zu schreiben. Ich verstehe, wie die Java-Sprache definiert ist; In diesem Fall habe ich die Methode aus irgendeinem Grund völlig mit etwas verwechselt, das es nicht war, obwohl ich diesen Fehler an anderer Stelle im selben Problem vermieden habe.
Magsol

0

Es ist sehr klar, dass es hier kein Konzept gibt, etwas zu überschreiben. Es ist eine Methodenüberladung. Die Object()Methode der Objektklasse verwendet einen Referenzparameter vom Typ Objekt und diese equal()Methode verwendet einen Referenzparameter vom Typ Test.


-1

Ich werde versuchen, dies anhand von zwei Beispielen zu erklären, bei denen es sich um die erweiterten Versionen einiger Beispiele handelt, die mir online begegnet sind.

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

Hier für Zeilen mit Zählwerten 0, 1, 2 und 3; Wir haben die Referenz von Object für o1 und t1 auf der equals()Methode. Daher wird beim Kompilieren die equals()Methode aus der Object.class- Datei begrenzt.

Doch obwohl Bezug von t1 ist Object , hat es intialization von Test - Klasse .
Object t1 = new Test();.
Daher ruft es zur Laufzeit das auf, public boolean equals(Object other)was ein ist

überschriebene Methode

. Geben Sie hier die Bildbeschreibung ein

Für Zählwerte als 4 und 6 ist es wieder einfach, dass t3, das Referenz und Initialisierung von Test hat, eine equals()Methode mit Parameter als Objektreferenzen aufruft und eine ist

überladene Methode

OK!

Um besser zu verstehen, welche Methode der Compiler aufruft, klicken Sie einfach auf die Methode, und Eclipse hebt die Methoden ähnlicher Typen hervor, von denen er glaubt, dass sie zur Kompilierungszeit aufgerufen werden. Wenn es zur Kompilierungszeit nicht aufgerufen wird, sind diese Methoden ein Beispiel für das Überschreiben von Methoden.

Geben Sie hier die Bildbeschreibung ein

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.