Java-Code für die Methode equals

75

Ich übe für eine Prüfung und habe ein Beispielproblem gefunden, das ich nicht verstehe.

Suchen Sie für den folgenden Code nach der Ausgabe:

public class Test {

    private static int count = 0;

    public boolean equals(Test testje) {
        System.out.println("count = " + count);
        return false;
    }

    public static void main(String [] args) {
        Object t1 = new Test();
        Object t2 = new Test();
        Test t3 = new Test();
        Object o1 = new Object();

        ++count; t1.equals(t2);
        ++count; t1.equals(t3);
        ++count; t3.equals(o1);
        ++count; t3.equals(t3);
        ++count; t3.equals(t2);
    }
}

Die Ausgabe dieses Codes ist count = 4, aber ich verstehe nicht warum. Kann mir jemand helfen?

Trocknet Coppens
quelle
32
Die richtige Antwort auf "Was macht Code wie dieser?" Sollte lauten: "Der zu clevere Programmierer wird gefeuert."
flauschig
Ich bin mit dem modernen Java nicht besonders vertraut. Können Sie auf die Cleverness @fluffy hinweisen?
Daniel
@Daniel siehe erans Antwort - es hat sehr unterschiedliche Verhaltensweisen, basierend auf dem Call-Site-Typ des Methodenparameters, dank einer absichtlich verwirrenden Methodenüberladung.
flauschig
1
Ich werde auch bemerken, dass die Verwendung niederländischer Variablennamen im Code stark verpönt ist.
Elva
1
Ich wünschte, ich könnte mich selbst finanzieren. Aber ich bin pleite :)
Daniel

Antworten:

112

Das erste , was man beachten sollte , ist , dass public boolean equals(Test testje) nicht nicht außer Kraft gesetzt Objectist equals, da das Argument Teststatt Object, so dass die Signaturen nicht übereinstimmen.

Daher mainruft die Methode equals(Test testje)bei der Ausführung genau einmal auf, t3.equals(t3);da dies der einzige Fall ist, in dem sowohl der statische Typ der Instanz equalsals auch der Typ des Arguments die TestKlasse sind.

t3.equals(t3);ist die 4. equalsAnweisung (die nach 4 Inkrementen der statischen countVariablen kommt), also werden 4 gedruckt.

Alle anderen equalsAnweisungen ausgeführt Objectist equalsund daher nichts drucken.

Eine detailliertere Erklärung:

t1.equals()Anrufe Objectsind equalsunabhängig von der Art des Arguments, da der statischen (Kompilierung) Typ t1ist Object, und die TestKlasse überschreibt nicht diese Methode. Die ObjectKlasse hat keine equalsMethode mit einem einzelnen TestArgument und equals(Test testje)kann daher unabhängig von der Dynamik (Laufzeitart) von nicht aufgerufen werden t1.

t3.equals()kann entweder Object's equalsoder Test' s gleich ausführen , da der Kompilierungszeittyp von t3ist Testund die TestKlasse zwei equalsMethoden hat (eine von der ObjectKlasse geerbt und die andere in der TestKlasse definiert ).

Die gewählte Methode hängt vom Kompilierungszeittyp des Arguments ab: 1. Wenn das Argument Object(wie in t3.equals(o1);oder t3.equals(t2);) lautet , wird Object's equalsaufgerufen und nichts gedruckt. 2. Wenn das Argument Testwie in ist t3.equals(t3);, equalsstimmen beide Versionen mit diesem Argument überein, aber aufgrund der Regeln der Methodenüberladung wird die Methode mit dem spezifischsten Argument - equals(Test testje)- ausgewählt und die countVariable gedruckt.

Eran
quelle
19
Meine Güte, deshalb verwende ich die Annotation @Override
Pierre Arlaud
11

Die Methode equals in Test verwendet eine Testinstanz.

Alle vorherigen Versuche wurden mit einer Instanz von Object durchgeführt, die die geerbte Methode aus der Object-Klasse übernimmt:

public boolean equals(Object o){
  return this == o;
}

Da dort kein Druck vorhanden ist, wird kein Wert gedruckt.

Sie ++count;erhöhen den Wert von count, also den Moment, in dem Sie Ihren tatsächlich anrufen

public boolean equals(Test testje){...

Methode, die diesen Wert druckt, ist der Wert von count 4.

Stultuske
quelle
7

t3.equals(t3)ist die einzige Zeile mit den richtigen Argumenten, die mit der Methodensignatur übereinstimmen. public boolean equals (Test testje)Es ist also die einzige Zeile im Programm, die diese print-Anweisung tatsächlich aufruft. Diese Frage soll Ihnen einige Dinge beibringen.

  • Alle Klassen erweitern implizit Object
  • Object.java enthält eine Methode equals, die den Typ Object annimmt
  • Es können mehrere Methoden mit demselben Namen vorhanden sein, sofern sie unterschiedliche Argumente haben. Dies wird als Methodenüberladung bezeichnet
  • Die Methodenmethodenüberladung, deren Signatur zur Laufzeit mit den Argumenten übereinstimmt, ist die Methode, die aufgerufen wird.

Der Trick dabei ist im Wesentlichen, dass Test Object implizit erweitert, wie es alle Java-Klassen tun. Object enthält eine equals-Methode vom Typ Object. t1 und t2 werden so typisiert, dass die Argumente zur Laufzeit niemals mit der in Test definierten Methodensignatur von equals übereinstimmen. Stattdessen wird immer die Methode equals in Object.java aufgerufen, da entweder der Basistyp Is Object ist. In diesem Fall haben Sie nur Zugriff auf die in Object.java definierten Methoden, oder der abgeleitete Typ ist Object. In diesem Fall

public boolean equals(Test testje)

Kann nicht eingegeben werden, da in diesem Fall das Argument zur Laufzeit vom Typ Object ist, bei dem es sich um eine Superklasse des Tests und nicht um eine Unterklasse handelt. Stattdessen wird die Methode equals in der implizit typisierten Superklasse Object.java von Test.java betrachtet, die auch eine Methode equals enthält, die zufällig eine Methodensignatur von hat

public boolean equals (Object o)

die in diesem Fall mit unseren Argumenten zur Laufzeit übereinstimmen, sodass diese Methode gleich ist, die ausgeführt wird.

Beachten Sie, dass t3.equals(t3)sowohl der Basistyp als auch der abgeleitete Typ von t3 Test sind.

Test t3 = new Test ();

Dies bedeutet, dass Sie zur Laufzeit die Methode equals in Test.java aufrufen und das Argument, das Sie übergeben, tatsächlich vom Typ Test ist, sodass die Methodensignaturen übereinstimmen und der Code in Test.java ausgeführt wird. An diesem Punkt count == 4.

Bonuswissen für Sie:

@Override 

Anmerkungen, die Sie möglicherweise an einigen Stellen gesehen haben, weisen den Compiler explizit an, fehlzuschlagen, wenn er irgendwo in einer Superklasse keine Methode mit genau derselben Signatur findet. Dies ist hilfreich, um zu wissen, ob Sie definitiv beabsichtigen , eine Methode zu überschreiben, und ob Sie absolut sicher sein möchten, dass Sie die Methode wirklich überschreiben und die Methode weder in der Oberklasse noch in der Unterklasse versehentlich geändert haben, jedoch nicht in beiden, und einen Laufzeitfehler eingeführt haben wobei die falsche Implementierung der Methode aufgerufen wird und unerwünschtes Verhalten verursacht.

james_s_tayler
quelle
4

Es gibt zwei wichtige Dinge, die Sie wissen sollten.

  • Überschriebene Methoden müssen genau die Signaturen haben, die ihre Oberklasse hat. (In Ihrem Beispiel erfüllt diese Bedingung nicht.)

  • In Java für ein Objekt haben wir zwei Typen: Kompilierung Typen und Laufzeittyp . Im folgenden Beispiel ist der Kompilierungstyp von myobjis, Objectaber der Laufzeittyp ist Car.

    public class Car{
          @Override
          public boolean equals(Object o){
                System.out.println("something");
                return false;
          }
    }
    

    Object myobj = new Car();

    Beachten Sie auch, dass dies myobj.equals(...)zum Drucken somethingin der Konsole führt.

Frogatto
quelle
1
@Override wird nicht benötigt, zum Teufel existierte es überhaupt nicht bis 1.5
Plugwash
@plugwash ist richtig. Du brauchst @Override nicht. Wenn Sie in diesem Fall @Override hinzufügen, wird der Code wahrscheinlich nicht mehr kompiliert, da equalsdie Oberklasse keine Methode mit derselben Methodensignatur enthält. @Override Annotation soll dem Compiler mitteilen, dass es in einer Oberklasse etwas mit dieser genauen Methodensignatur geben sollte - bitte beschweren Sie sich, wenn dies nicht der
Fall
Ja, deshalb verwenden Sie @Override. Wenn Sie also beabsichtigen, etwas zu überschreiben, aber die Details vermasselt haben, schreit der Compiler Sie an, anstatt stattdessen stillschweigend zu überladen.
Plugwash
@plugwash Danke für den Hinweis.
Frogatto