Dieses Problem tritt am deutlichsten auf, wenn Sie unterschiedliche Implementierungen einer Schnittstelle haben und sich für die Zwecke einer bestimmten Sammlung nur um die Ansicht der Objekte auf Schnittstellenebene kümmern. Angenommen, Sie hatten eine Schnittstelle wie diese:
public interface Person {
int getId();
}
Die übliche Art hashcode()
und Weise, equals()
Klassen zu implementieren und zu implementieren, würde in der equals
Methode folgenden Code enthalten :
if (getClass() != other.getClass()) {
return false;
}
Dies verursacht Probleme, wenn Sie Implementierungen von Person
in a mischen HashMap
. Wenn HashMap
sich der Benutzer nur um die Ansicht auf Schnittstellenebene kümmert Person
, kann dies zu Duplikaten führen, die sich nur in den implementierenden Klassen unterscheiden.
Sie könnten diesen Fall unter Verwendung der gleichen liberalen equals()
Methode für alle Implementierungen zum Laufen bringen, aber dann laufen Sie Gefahr equals()
, in einem anderen Kontext das Falsche zu tun (z. B. zwei Person
s zu vergleichen, die durch Datenbankdatensätze mit Versionsnummern unterstützt werden).
Meine Intuition sagt mir, dass Gleichheit pro Sammlung anstatt pro Klasse definiert werden sollte. Wenn Sie Sammlungen verwenden, die auf der Bestellung basieren, können Sie mithilfe einer benutzerdefinierten Comparator
Reihenfolge die richtige Reihenfolge für jeden Kontext auswählen. Es gibt kein Analogon für Hash-basierte Sammlungen. Warum ist das?
Zur Verdeutlichung unterscheidet sich diese Frage von " Warum befindet sich .compareTo () in einer Schnittstelle, während .equals () in einer Klasse in Java enthalten ist? ", Da sie sich mit der Implementierung von Auflistungen befasst. compareTo()
und equals()
/ oder hashcode()
beide haben das Problem der Universalität bei der Verwendung von Sammlungen: Sie können nicht verschiedene Vergleichsfunktionen für verschiedene Sammlungen auswählen. Für die Zwecke dieser Frage spielt die Vererbungshierarchie eines Objekts also keine Rolle. Alles, was zählt, ist, ob die Vergleichsfunktion pro Objekt oder pro Sammlung definiert ist.
quelle
Person
das die erwartete Umsetzungequals
undhashCode
Verhalten. Sie hätten dann eineHashMap<PersonWrapper, V>
. Dies ist ein Beispiel, in dem ein Pure-OOP-Ansatz nicht elegant ist: Nicht jede Operation an einem Objekt ist als Methode dieses Objekts sinnvoll. Java ganzeObject
Art ist eine Mischung aus unterschiedlichen Zuständigkeiten - nur diegetClass
,finalize
undtoString
Methoden scheinen fern gerechtfertigt durch die heutigen Best Practices.IEqualityComparer<T>
an eine Hash-basierte Sammlung übergeben. Wenn Sie keine angeben, wird eine Standardimplementierung verwendet, die aufObject.Equals
und basiertObject.GetHashCode()
. 2) Das ÜberschreibenEquals
eines veränderlichen Referenztyps durch IMO ist selten eine gute Idee. Auf diese Weise ist die Standardgleichheit ziemlich streng, aber Sie können eine lockerere Gleichheitsregel verwenden, wenn Sie sie über eine benutzerdefinierte benötigenIEqualityComparer<T>
.Antworten:
Dieses Design wird manchmal als "Universal Equality" bezeichnet. Man glaubt, dass es eine universelle Eigenschaft ist, ob zwei Dinge gleich sind oder nicht.
Darüber hinaus ist Gleichheit eine Eigenschaft von zwei Objekten, aber in OO rufen Sie immer eine Methode für ein einzelnes Objekt auf , und dieses Objekt entscheidet allein, wie dieser Methodenaufruf behandelt wird. In einem Entwurf wie dem von Java, in dem Gleichheit eine Eigenschaft eines der beiden zu vergleichenden Objekte ist, können einige grundlegende Eigenschaften der Gleichheit wie Symmetrie nicht einmal garantiert werden (
a == b
⇔b == a
) , da im ersten Fall die Methode wird angerufena
und im zweiten Fall wird es angerufenb
und aufgrund der Grundprinzipien von OO ist es alleina
die Entscheidung (im ersten Fall) oderb
Entscheidung (im zweiten Fall), ob es sich dem anderen gleich sieht oder nicht. Die einzige Möglichkeit, Symmetrie zu erlangen, besteht darin, die beiden Objekte zusammenarbeiten zu lassen, aber wenn sie nicht ... Pech haben.Eine Lösung wäre, die Gleichheit nicht zu einer Eigenschaft eines Objekts zu machen, sondern entweder zu einer Eigenschaft zweier Objekte oder zu einer Eigenschaft eines dritten Objekts. Diese letztere Option löst auch das Problem der universellen Gleichheit, denn wenn Sie Gleichheit zu einer Eigenschaft eines dritten "Kontext" -Objekts machen, können Sie sich vorstellen, unterschiedliche
EqualityComparer
Objekte für unterschiedliche Kontexte zu haben.Dies ist das Design, das für Haskell zum Beispiel für die
Eq
Typenklasse gewählt wurde. Es ist auch das Design, das von einigen Scala-Bibliotheken von Drittanbietern (z. B. ScalaZ) gewählt wurde, nicht jedoch von der Scala-Kern- oder Standardbibliothek, die universelle Gleichheit für die Kompatibilität mit der zugrunde liegenden Hostplattform verwendet.Interessanterweise ist es auch das Design, das mit Javas
Comparable
/Comparator
Interfaces gewählt wurde. Die Entwickler von Java waren sich des Problems klar bewusst, lösten es jedoch aus irgendeinem Grund nur zur Bestellung, nicht aber zur Gleichheit (oder zum Hashing).Also, was die Frage betrifft
Die Antwort lautet "Ich weiß nicht". Die Entwickler von Java waren sich des Problems
Comparator
durchaus bewusst, wie die Existenz von beweist , aber sie hielten es offensichtlich nicht für ein Problem für Gleichheit und Hasching. Andere Sprachen und Bibliotheken treffen andere Entscheidungen.quelle
The methods equals and hashCode are declared for the benefit of hashtables such as java.util.Hashtable
dh beides,equals
undhashCode
wurdenObject
von Java-Entwicklern ausschließlich zum Zweck der Einführung als Methoden eingeführtHashtable
- es gibt keine Vorstellung von UE oder etwas Silimarem in der Spezifikation, und das Zitat ist für mich klar genug. wenn nicht für dieHashtable
,equals
wäre das wohl in einer schnittstelle gefallenComparable
. Während ich Ihre Antwort früher für richtig hielt, halte ich sie jetzt für unbegründet.clone
- es war ursprünglich ein Operator , keine Methode (siehe Oak Language Specification), Zitat:The unary operator clone is applied to an object. (...) The clone operator is normally used inside new to clone the prototype of some class, before applying the initializers (constructors)
- die drei keyword-ähnlichen Operatoren wareninstanceof new clone
(Abschnitt 8.1, Operatoren). Ich nehme an, das ist der wahre (historische) Grund für dasclone
/Cloneable
chaos - esCloneable
war einfach eine spätere Erfindung und der vorhandeneclone
Code wurde damit nachgerüstet.Die wahre Antwort auf
ist, Zitat mit freundlicher Genehmigung von Josh Bloch :
Das Problem liegt nur in Java Geschichte, wie bei anderen ähnlichen Angelegenheiten, zB
.clone()
vsCloneable
.tl; dr
es ist hauptsächlich aus historischen Gründen; Das aktuelle Verhalten / die aktuelle Abstraktion wurde in JDK 1.0 eingeführt und später nicht behoben, da dies bei Aufrechterhaltung der Abwärtscodekompatibilität praktisch unmöglich war.
Fassen wir zunächst einige bekannte Java-Fakten zusammen:
Hashtable
,.hashCode()
&.equals()
wurden in JDK 1.0 implementiert ( Hashtable )Comparable
/Comparator
wurde in JDK 1.2 ( Vergleichbar ) eingeführt,Nun folgt es:
.hashCode()
und.equals()
dabei die Abwärtskompatibilität beizubehalten, nachdem die Leute erkannt hatten, dass es bessere Abstraktionen gibt, als sie in Superobjekten zu platzieren, weil z. B. jeder einzelne Java-Programmierer von 1.2 wusste, dass jederObject
sie hat und sie hat Dort physisch zu bleiben, um auch Kompatibilität mit kompiliertem Code (JVM) zu gewährleisten - und eine explizite Schnittstelle zu jederObject
Unterklasse hinzuzufügen , die sie tatsächlich implementiert hat, würde dieses Durcheinander gleichClonable
eins machen (sic!) ( Bloch erläutert, warum klonbar ist , siehe auch EJ 2nd) und viele andere Orte, einschließlich SO),Nun fragen Sie sich vielleicht: "Was hat
Hashtable
das mit all dem zu tun?"Die Antwort lautet:
hashCode()
/equals()
Vertrag und weniger gute Sprachdesign-Kenntnisse der Java-Kernentwickler 1995/1996.Zitat aus der Java 1.0-Sprachspezifikation vom 1996 - 4.3.2 Die Klasse
Object
, S.41:(beachten Sie diese genaue Aussage wurde geändert in späteren Versionen, zu sagen, Zitat:
The method hashCode is very useful, together with the method equals, in hashtables such as java.util.HashMap.
, was es unmöglich macht , die direkt zu machenHashtable
-hashCode
-equals
Verbindung ohne historische JLS zu lesen!)Das Java-Team entschied sich für eine Sammlung im Stil eines guten Wörterbuchs und erstellte diese
Hashtable
(bisher gute Idee), wollte aber, dass der Programmierer sie mit möglichst wenig Code- / Lernaufwand verwenden kann (Hoppla! Probleme beim Empfang!). und da es noch keine generika gab [es ist ja doch JDK 1.0], würde das bedeuten, dass entweder jederObject
put inHashtable
explizit eine schnittstelle implementieren müsste (und die schnittstellen waren damals noch in den Anfängen ...Comparable
noch nicht einmal!) Dies zu einer Abschreckung für viele zu machen - oder implizit eine Hash-MethodeObject
zu implementieren.Offensichtlich haben sie sich aus den oben genannten Gründen für Lösung 2 entschieden. Ja, jetzt wissen wir, dass sie falsch lagen. ... es ist einfach, im Nachhinein schlau zu sein. Kichern
hashCode()
Erfordertequals()
nun, dass jedes Objekt, das es hat, eine eigene Methode haben muss - daher war es ziemlich offensichtlich, dassequals()
dies auch eingegeben werden mussteObject
.Da die Standardimplementierungen dieser Methoden auf valid
a
&b
Object
s im Wesentlichen durch Redundanz nutzlos sind (a.equals(b)
Gleichstellung mita==b
unda.hashCode() == b.hashCode()
ungefähr Gleichstellung mita==b
, es sei dennhashCode
und / oder sieequals
werden überschrieben, oder Sie GC Hunderttausende vonObject
s während des Lebenszyklus Ihrer Anwendung 1 ). Es ist sicher zu sagen, dass sie hauptsächlich als Sicherungsmaßnahme und aus Gründen der Benutzerfreundlichkeit bereitgestellt wurden. Genau so kommen wir zu der bekannten Tatsache, dass immer beides außer Kraft gesetzt wird.equals()
und.hashCode()
wenn Sie beabsichtigen, die Objekte tatsächlich zu vergleichen oder sie mit Hash zu speichern. Das Überschreiben von nur einem Code ohne den anderen ist eine gute Möglichkeit, Ihren Code zu verfälschen (durch schlechte Vergleichsergebnisse oder wahnsinnig hohe Bucket-Kollisionswerte) - und für Anfänger ist es eine Quelle ständiger Verwirrung und Fehler (suchen Sie nach SO, um zu sehen) es für sich selbst) und ständige Belästigung für erfahrene.Beachten Sie auch, dass C # zwar besser mit Equals & Hashcode umgeht, aber Eric Lippert selbst angibt, dass er mit C # fast den gleichen Fehler begangen hat , den Sun mit Java vor C # gemacht hat :
1 natürlich
Object#hashCode
kann immer noch kollidieren, aber es ein wenig Mühe nimmt , das zu tun, siehe: http://bugs.java.com/bugdatabase/view_bug.do?bug_id=6809470 und verknüpften Fehlerberichte für Details; https://stackoverflow.com/questions/1381060/hashcode-uniqueness/1381114#1381114 behandelt dieses Thema ausführlicher.quelle
Object
der Gestaltung; Hashbarkeit war.