Die wahre Antwort auf
warum gibt es eine Comparator
schnittstelle aber nein Hasher
und Equator
?
ist, Zitat mit freundlicher Genehmigung von Josh Bloch :
Die ursprünglichen Java-APIs wurden innerhalb kürzester Zeit erstellt, um ein schließendes Marktfenster zu erreichen. Das ursprüngliche Java-Team hat einen unglaublichen Job gemacht, aber nicht alle APIs sind perfekt.
Das Problem liegt nur in Java Geschichte, wie bei anderen ähnlichen Angelegenheiten, zB .clone()
vs Cloneable
.
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:
- Java war von Anfang an bis heute mit Stolz abwärtskompatibel und erforderte, dass ältere APIs in neueren Versionen weiterhin unterstützt werden.
- als solches hat fast jedes mit JDK 1.0 eingeführte Sprachkonstrukt bis heute gelebt,
Hashtable
, .hashCode()
& .equals()
wurden in JDK 1.0 implementiert ( Hashtable )
Comparable
/ Comparator
wurde in JDK 1.2 ( Vergleichbar ) eingeführt,
Nun folgt es:
- Es war praktisch unmöglich und sinnlos, verschiedene Schnittstellen nachzurüsten
.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 jeder Object
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 jeder Object
Unterklasse hinzuzufügen , die sie tatsächlich implementiert hat, würde dieses Durcheinander gleich Clonable
eins machen (sic!) ( Bloch erläutert, warum klonbar ist , siehe auch EJ 2nd) und viele andere Orte, einschließlich SO),
- Sie ließen sie einfach dort, damit die zukünftige Generation eine konstante Quelle von WTFs hat.
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:
Die Methoden equals
und hashCode
werden zum Nutzen von Hashtabellen wie java.util.Hashtable
(§21.7) deklariert. Die Methode equals definiert einen Begriff der Objektgleichheit, der auf dem Vergleich von Werten und nicht von Referenzen basiert.
(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 machen Hashtable
- 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 jeder Object
put in Hashtable
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-Methode Object
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, dass equals()
dies auch eingegeben werden musste Object
.
Da die Standardimplementierungen dieser Methoden auf valid a
& b
Object
s im Wesentlichen durch Redundanz nutzlos sind ( a.equals(b)
Gleichstellung mit a==b
und a.hashCode() == b.hashCode()
ungefähr Gleichstellung mit a==b
, es sei denn hashCode
und / oder sie equals
werden überschrieben, oder Sie GC Hunderttausende von Object
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 :
Aber warum sollte es der Fall sein, dass jedes Objekt in der Lage sein sollte, sich selbst für das Einfügen in eine Hash-Tabelle zu hashen? Scheint eine seltsame Sache zu sein, die von jedem Gegenstand verlangt, dass er es kann. Ich denke, wenn wir das Typensystem heute von Grund auf neu entwerfen würden, könnte das anders aussehen, vielleicht mit einer IHashable
Schnittstelle. Als das CLR-Typsystem entworfen wurde, gab es keine generischen Typen, und daher musste eine universelle Hash-Tabelle in der Lage sein, jedes Objekt zu speichern.
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.
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.