Gibt es einen Grund, getClass () der Instanz von .equals () vorzuziehen?

174

Ich verwende Eclipse zum Generieren von .equals()und .hashCode()und es gibt eine Option mit der Bezeichnung "Verwenden Sie 'instanceof' zum Vergleichen von Typen". Standardmäßig ist diese Option deaktiviert und wird .getClass()zum Vergleichen von Typen verwendet. Gibt es einen Grund , warum ich es vorziehen sollte .getClass()über instanceof?

Ohne zu verwenden instanceof:

if (obj == null)
  return false;
if (getClass() != obj.getClass())
  return false;

Verwenden von instanceof:

if (obj == null)
  return false;
if (!(obj instanceof MyClass))
  return false;

Normalerweise überprüfe ich die instanceofOption und gehe dann hinein und entferne den " if (obj == null)" Scheck. (Es ist redundant, da Nullobjekte immer fehlschlagen instanceof.) Gibt es einen Grund, der eine schlechte Idee ist?

Pennen
quelle
1
Der Ausdruck x instanceof SomeClassist falsch , wenn xist null. Daher benötigt die zweite Syntax keine Nullprüfung.
rds
5
@rds Ja, der Absatz unmittelbar nach dem Code-Snippet sagt dies ebenfalls aus. Es befindet sich im Code-Snippet, da Eclipse dies generiert.
Kip

Antworten:

100

Wenn Sie verwenden instanceof, wird bei Ihrer equalsImplementierung finalder Symmetrievertrag der Methode beibehalten : x.equals(y) == y.equals(x). Wenn dies finalrestriktiv erscheint, überprüfen Sie sorgfältig Ihren Begriff der Objektäquivalenz, um sicherzustellen, dass Ihre übergeordneten Implementierungen den von der ObjectKlasse festgelegten Vertrag vollständig beibehalten .

erickson
quelle
Genau das kam mir in den Sinn, als ich das Zitat von Josh Bloch oben las. +1 :)
Johannes Schaub - litb
13
definitiv die Schlüsselentscheidung. Symmetrie muss zutreffen, und Instanz macht es sehr leicht, versehentlich asymmetrisch zu sein
Scott Stanchfield
Ich würde auch hinzufügen, dass "wenn finales restriktiv erscheint" (für beide equalsund hashCode) dann getClass()Gleichheit anstelle von instanceofverwenden, um die Symmetrie- und Transitivitätsanforderungen des equalsVertrags zu erhalten.
Shadow Man
176

Josh Bloch bevorzugt Ihren Ansatz:

Der Grund, warum ich den instanceofAnsatz bevorzuge, ist, dass Sie bei Verwendung des getClassAnsatzes die Einschränkung haben, dass Objekte nur anderen Objekten derselben Klasse und demselben Laufzeittyp entsprechen. Wenn Sie eine Klasse erweitern und ein paar harmlose Methoden hinzufügen, prüfen Sie, ob ein Objekt der Unterklasse einem Objekt der Superklasse entspricht, auch wenn die Objekte in allen wichtigen Aspekten gleich sind überraschende Antwort, dass sie nicht gleich sind. Tatsächlich verstößt dies gegen eine strikte Interpretation des Liskov-Substitutionsprinzips und kann zu sehr überraschendem Verhalten führen. In Java ist dies besonders wichtig, da die meisten Sammlungen (HashTableusw.) basieren auf der Gleichheitsmethode. Wenn Sie ein Mitglied der Superklasse als Schlüssel in eine Hash-Tabelle einfügen und es dann mit einer Unterklasseninstanz nachschlagen, werden Sie es nicht finden, da sie nicht gleich sind.

Siehe auch diese SO-Antwort .

Effektives Java- Kapitel 3 behandelt dies ebenfalls.

Michael Myers
quelle
18
+1 Jeder, der Java macht, sollte dieses Buch mindestens 10 Mal lesen!
André
16
Das Problem bei der Instanz des Ansatzes besteht darin, dass die "symmetrische" Eigenschaft von Object.equals () dadurch unterbrochen wird, dass x.equals (y) == true, aber y.equals (x) == false bei x.getClass möglich wird ()! = y.getClass (). Ich ziehe es vor, diese Eigenschaft nicht zu brechen, es sei denn, dies ist unbedingt erforderlich (dh das Überschreiben von equals () für verwaltete Objekte oder Proxy-Objekte).
Kevin Sitze
8
Die Instanz des Ansatzes ist richtig, wenn und nur wenn die Basisklasse definiert, was Gleichheit zwischen Unterklassenobjekten bedeuten soll. Die Verwendung getClassverstößt nicht gegen den LSP, da sich der LSP lediglich darauf bezieht, was mit vorhandenen Instanzen getan werden kann - nicht darauf, welche Arten von Instanzen erstellt werden können. Die von zurückgegebene Klasse getClassist eine unveränderliche Eigenschaft einer Objektinstanz. Der LSP impliziert nicht, dass es möglich sein sollte, eine Unterklasse zu erstellen, in der diese Eigenschaft eine andere Klasse als die anzeigt, die sie erstellt hat.
Supercat
66

Angelika Langers Secrets of Equals geht darauf mit einer langen und detaillierten Diskussion einiger allgemeiner und bekannter Beispiele ein, darunter von Josh Bloch und Barbara Liskov, und entdeckt in den meisten von ihnen einige Probleme. Sie bekommt auch in den instanceofvs getClass. Einige zitieren daraus

Schlussfolgerungen

Was schließen wir, nachdem wir die vier willkürlich ausgewählten Beispiele für Implementierungen von equals () analysiert haben?

Erstens: Es gibt zwei wesentlich unterschiedliche Möglichkeiten, die Überprüfung auf Typübereinstimmung in einer Implementierung von equals () durchzuführen. Eine Klasse kann mithilfe des Operators instanceof einen Vergleich gemischter Typen zwischen Objekten der Ober- und Unterklasse ermöglichen, oder eine Klasse kann Objekte unterschiedlichen Typs mithilfe des Tests getClass () als ungleich behandeln. Die obigen Beispiele haben deutlich gemacht, dass Implementierungen von equals () mit getClass () im Allgemeinen robuster sind als Implementierungen mit instanceof.

Die Testinstanz ist nur für Abschlussklassen korrekt oder wenn mindestens method equals () in einer Oberklasse endgültig ist. Letzteres impliziert im Wesentlichen, dass keine Unterklasse den Status der Oberklasse erweitern darf, sondern nur Funktionen oder Felder hinzufügen kann, die für den Status und das Verhalten des Objekts irrelevant sind, z. B. transiente oder statische Felder.

Implementierungen mit dem getClass () -Test entsprechen dagegen immer dem equals () -Vertrag; Sie sind korrekt und robust. Sie unterscheiden sich jedoch semantisch stark von Implementierungen, die die Testinstanz verwenden. Implementierungen mit getClass () erlauben keinen Vergleich von Unter- mit Oberklassenobjekten, auch wenn die Unterklasse keine Felder hinzufügt und nicht einmal equals () überschreiben möchte. Eine solche "triviale" Klassenerweiterung wäre beispielsweise das Hinzufügen einer Debug-Print-Methode in einer Unterklasse, die genau für diesen "trivialen" Zweck definiert ist. Wenn die Oberklasse einen Vergleich gemischter Typen über die Prüfung getClass () verbietet, wäre die triviale Erweiterung nicht mit ihrer Oberklasse vergleichbar. Ob dies ein Problem ist oder nicht, hängt vollständig von der Semantik der Klasse und dem Zweck der Erweiterung ab.

Johannes Schaub - litb
quelle
61

Der Grund für die Verwendung getClassbesteht darin, die symmetrische Eigenschaft des equalsVertrags sicherzustellen . Aus den JavaDocs von equals:

Es ist symmetrisch: Für alle Nicht-Null-Referenzwerte x und y sollte x.equals (y) genau dann true zurückgeben, wenn y.equals (x) true zurückgibt.

Durch die Verwendung von instanceof ist es möglich, nicht symmetrisch zu sein. Betrachten Sie das Beispiel: Hund erweitert Tier. Tier equalstut eine instanceofÜberprüfung des Tieres. Hund equalstut einen instanceofScheck von Dog. Geben Sie Tier a und Hund d (mit anderen Feldern gleich):

a.equals(d) --> true
d.equals(a) --> false

Dies verletzt die symmetrische Eigenschaft.

Um den Vertrag von Equal genau zu befolgen, muss die Symmetrie sichergestellt sein, und daher muss die Klasse dieselbe sein.

Steve Kuo
quelle
8
Das ist die erste klare und präzise Antwort auf diese Frage. Ein Codebeispiel sagt mehr als tausend Worte.
Johnride
Ich habe alle anderen ausführlichen Antworten durchgesehen, indem Sie den Tag gerettet haben. Einfach, präzise, ​​elegant und absolut die beste Antwort auf diese Frage.
1
Es gibt die Symmetrieanforderung, wie Sie erwähnt haben. Es gibt aber auch die Anforderung "Transitivität". Wenn a.equals(c)und b.equals(c)dann a.equals(b)(der naive Ansatz, Dog.equalsnur return super.equals(object)wann zu machen, !(object instanceof Dog)aber die zusätzlichen Felder zu überprüfen, wenn es sich um eine Dog-Instanz handelt, würde die Symmetrie nicht verletzen, aber die Transitivität verletzen)
Shadow Man
Um sicher zu gehen, können Sie eine getClass()Gleichheitsprüfung verwenden, oder Sie können eine instanceofPrüfung verwenden, wenn Sie Ihre equalsund hashCodeMethoden erstellen final.
Shadow Man
26

Dies ist so etwas wie eine religiöse Debatte. Beide Ansätze haben ihre Probleme.

  • Wenn Sie instanceof verwenden , können Sie Unterklassen niemals signifikante Mitglieder hinzufügen.
  • Verwenden Sie getClass und Sie verstoßen gegen das Liskov-Substitutionsprinzip.

Bloch hat einen weiteren relevanten Ratschlag in Effective Java Second Edition :

  • Punkt 17: Entwurf und Dokument zur Vererbung oder zum Verbot
McDowell
quelle
1
Nichts über die Verwendung getClasswürde den LSP verletzen, es sei denn, die Basisklasse hat speziell ein Mittel dokumentiert, mit dem es möglich sein sollte, Instanzen verschiedener Unterklassen, die vergleichbar sind, gleich zu machen. Welche LSP-Verletzung sehen Sie?
Supercat
Vielleicht sollte das als Use getClass umformuliert werden und Sie lassen Subtypen in Gefahr von LSP-Verstößen. Bloch hat ein Beispiel in Effective Java - auch hier besprochen . Sein Grundprinzip ist größtenteils, dass es für Entwickler, die Subtypen implementieren, die keinen Status hinzufügen, zu überraschendem Verhalten führen kann. Ich verstehe Ihren Standpunkt - Dokumentation ist der Schlüssel.
McDowell
2
Zwei Instanzen unterschiedlicher Klassen sollten sich nur dann als gleich melden, wenn sie von einer gemeinsamen Basisklasse oder Schnittstelle erben, die definiert, was Gleichheit bedeutet [eine Philosophie, die Java, zweifelhaft IMHO, auf einige Sammlungsschnittstellen angewendet hat]. Sofern der Basisklassenvertrag getClass()nicht besagt, dass dies nicht über die Tatsache hinaus als sinnvoll angesehen werden sollte, dass die betreffende Klasse in die Basisklasse konvertierbar ist, sollte die Rückgabe von getClass()wie jede andere Eigenschaft betrachtet werden, die übereinstimmen muss, damit Instanzen gleich sind.
Supercat
1
@eyalzba Wenn instanceof_ verwendet wird, wenn eine Unterklasse Mitglieder zum Gleichstellungsvertrag hinzufügt, würde dies die Anforderung der symmetrischen Gleichheit verletzen . Die Verwendung von getClass- Unterklassen kann niemals dem übergeordneten Typ entsprechen. Nicht, dass Sie sowieso gleich überschreiben sollten, aber dies ist der Kompromiss, wenn Sie dies tun.
McDowell
2
"Design und Dokument für die Vererbung oder verbieten" Ich denke, ist sehr wichtig. Nach meinem Verständnis sollten Sie Gleichheit nur überschreiben, wenn Sie einen unveränderlichen Werttyp erstellen. In diesem Fall sollte die Klasse als endgültig deklariert werden. Da es keine Vererbung gibt, können Sie nach Bedarf Gleichheit implementieren. In den Fällen, in denen Sie die Vererbung zulassen, sollten Objekte anhand der Referenzgleichheit verglichen werden.
TheSecretSquad
23

Korrigieren Sie mich, wenn ich falsch liege, aber getClass () ist hilfreich, wenn Sie sicherstellen möchten, dass Ihre Instanz KEINE Unterklasse der Klasse ist, mit der Sie vergleichen. Wenn Sie in dieser Situation instanceof verwenden, können Sie das NICHT wissen, weil:

class A { }

class B extends A { }

Object oA = new A();
Object oB = new B();

oA instanceof A => true
oA instanceof B => false
oB instanceof A => true // <================ HERE
oB instanceof B => true

oA.getClass().equals(A.class) => true
oA.getClass().equals(B.class) => false
oB.getClass().equals(A.class) => false // <===============HERE
oB.getClass().equals(B.class) => true
TraderJoeChicago
quelle
Für mich ist dies der Grund
karlihnos
5

Wenn Sie sicherstellen möchten, dass nur diese Klasse übereinstimmt, verwenden Sie getClass() ==. Wenn Sie Unterklassen abgleichen möchten, instanceofwird dies benötigt.

Außerdem stimmt instanceof nicht mit einer Null überein, kann aber sicher mit einer Null verglichen werden. Sie müssen es also nicht null überprüfen.

if ( ! (obj instanceof MyClass) ) { return false; }
Clint
quelle
Zu Ihrem zweiten Punkt: Er hat das bereits in der Frage erwähnt. "Normalerweise überprüfe ich die Option instanceof und entferne dann die Prüfung 'if (obj == null)'."
Michael Myers
5

Dies hängt davon ab, ob Sie berücksichtigen, ob eine Unterklasse einer bestimmten Klasse der übergeordneten Klasse entspricht.

class LastName
{
(...)
}


class FamilyName
extends LastName
{
(..)
}

Hier würde ich 'instanceof' verwenden, da ich möchte, dass ein Nachname mit dem Familiennamen verglichen wird

class Organism
{
}

class Gorilla extends Organism
{
}

hier würde ich 'getClass' verwenden, da die Klasse bereits sagt, dass die beiden Instanzen nicht gleichwertig sind.

Pierre
quelle
3

instanceof arbeitet für Instenzen derselben Klasse oder ihrer Unterklassen

Sie können damit testen, ob ein Objekt eine Instanz einer Klasse, eine Instanz einer Unterklasse oder eine Instanz einer Klasse ist, die eine bestimmte Schnittstelle implementiert.

ArryaList und RoleList sind beide Instanzen von List

Während

getClass () == o.getClass () ist nur wahr, wenn beide Objekte (this und o) genau derselben Klasse angehören.

Je nachdem, was Sie vergleichen müssen, können Sie das eine oder andere verwenden.

Wenn Ihre Logik lautet: "Ein Objekt ist nur dann gleich einem anderen, wenn beide dieselbe Klasse sind", sollten Sie sich für "gleich" entscheiden, was meiner Meinung nach in den meisten Fällen der Fall ist.

OscarRyz
quelle
3

Beide Methoden haben ihre Probleme.

Wenn die Unterklasse die Identität ändert, müssen Sie ihre tatsächlichen Klassen vergleichen. Andernfalls verletzen Sie die symmetrische Eigenschaft. Beispielsweise sollten verschiedene Arten von Persons nicht als gleichwertig angesehen werden, selbst wenn sie denselben Namen haben.

Einige Unterklassen ändern jedoch nicht die Identität und müssen verwendet werden instanceof. Wenn wir zum Beispiel eine Reihe unveränderlicher ShapeObjekte haben, sollte a Rectanglemit einer Länge und Breite von 1 gleich der Einheit sein Square.

In der Praxis denke ich, dass der erstere Fall eher wahr ist. Normalerweise ist die Unterklasse ein wesentlicher Bestandteil Ihrer Identität. Wenn Sie genau wie Ihre Eltern sind, außer dass Sie eine Kleinigkeit tun können, sind Sie nicht gleichberechtigt.

James
quelle
-1

Tatsächlich wird geprüft, wo ein Objekt zu einer Hierarchie gehört oder nicht. Beispiel: Das Autoobjekt gehört zur Fahrzeugklasse. "New Car () Instanz von Vehical" gibt also true zurück. Und "new Car (). GetClass (). Equals (Vehical.class)" gibt false zurück, obwohl das Car-Objekt zur Vehical-Klasse gehört, aber als separater Typ kategorisiert ist.

Akash5288
quelle