La vera risposta a
perché c'è Comparator
un'interfaccia ma no Hasher
e Equator
?
è, citazione per gentile concessione di Josh Bloch :
Le API Java originali sono state eseguite molto rapidamente in tempi ristretti per soddisfare una finestra di mercato di chiusura. Il team Java originale ha fatto un lavoro incredibile, ma non tutte le API sono perfette.
Il problema sta solo nella storia di Java, come per altre questioni simili, ad esempio .clone()
vs Cloneable
.
tl; dr
è principalmente per ragioni storiche; l'attuale comportamento / astrazione è stato introdotto in JDK 1.0 e non è stato corretto in seguito perché era praticamente impossibile farlo mantenendo la compatibilità con il codice a ritroso.
Innanzitutto, riassumiamo un paio di fatti Java noti:
- Java, dall'inizio ai giorni nostri, era orgogliosamente compatibile con le versioni precedenti, richiedendo che le API legacy fossero ancora supportate nelle versioni più recenti,
- in quanto tale, quasi ogni costrutto del linguaggio introdotto con JDK 1.0 è vissuto fino ai giorni nostri,
Hashtable
, .hashCode()
E .equals()
sono state realizzate in JDK 1.0, ( Hashtable )
Comparable
/ è Comparator
stato introdotto in JDK 1.2 ( comparabile ),
Ora segue:
- era praticamente impossibile e insensato retrofit
.hashCode()
e .equals()
interfacce distinte, pur mantenendo la retrocompatibilità dopo che le persone si resero conto che ci sono astrazioni migliori che metterle in superoggetto, perché ad esempio tutti i programmatori Java di 1.2 sapevano che ognuno Object
li ha, e avevano rimanere lì fisicamente per fornire anche la compatibilità del codice compilato (JVM) - e aggiungere un'interfaccia esplicita a ogni Object
sottoclasse che le implementasse davvero renderebbe questo disordine uguale (sic!) a Clonable
uno ( Bloch discute perché fa schifo Cloneable , anche discusso in ad esempio EJ 2nd e molti altri luoghi, incluso SO),
- li hanno appena lasciati lì per la generazione futura di avere una fonte costante di WTF.
Ora, potresti chiedere "che cosa Hashtable
ha tutto questo"?
La risposta è: hashCode()
/ equals()
contratto e abilità linguistiche non così buone degli sviluppatori Java principali nel 1995/1996.
Citazione da Java 1.0 Language Spec, datata 1996 - 4.3.2 The Class Object
, p.41:
I metodi equals
e hashCode
sono dichiarati a beneficio di hashtables come java.util.Hashtable
(§21.7). Il metodo equals definisce una nozione di uguaglianza di oggetti, che si basa sul confronto di valore, non di riferimento.
(nota che questa affermazione esatta è stata modificata nelle versioni successive, per esempio, citando:, The method hashCode is very useful, together with the method equals, in hashtables such as java.util.HashMap.
rendendo impossibile effettuare la connessione diretta Hashtable
- hashCode
- equals
senza leggere la JLS storica!)
Il team Java ha deciso di desiderare una buona raccolta in stile dizionario e ha creato Hashtable
(buona idea finora), ma voleva che il programmatore fosse in grado di usarlo con il minor numero di curve di apprendimento / codice (oops! Guai in arrivo!) - e, poiché non vi era ancora [it del JDK 1.0, dopo tutto] non generici, ciò significherebbe che o ogni Object
put in Hashtable
avrebbe dovuto implementare esplicitamente alcuni di interfaccia (e le interfacce erano ancora solo nella loro inizio allora ... non Comparable
ancora, anche!) , rendendolo un deterrente per usarlo per molti - o Object
dovrebbe implementare implicitamente un metodo di hashing.
Ovviamente, sono andati con la soluzione 2, per i motivi indicati sopra. Sì, ora sappiamo che avevano torto. ... è facile essere intelligenti col senno di poi. ridacchiare
Ora, hashCode()
richiede che ogni oggetto che lo possiede deve avere un equals()
metodo distinto - quindi era abbastanza ovvio che equals()
doveva essere inserito Object
anche.
Dal momento che i predefiniti implementazioni di questi metodi su valida a
& b
Object
s sono sostanzialmente inutile per essere ridondante (rendendo a.equals(b)
uguale a a==b
e a.hashCode() == b.hashCode()
approssimativamente uguale a a==b
anche, a meno hashCode
e / o equals
viene sovrascritto, oppure GC centinaia di migliaia di Object
s durante il ciclo di vita dell'applicazione 1 ) , è sicuro di dire che sono stati forniti principalmente come misura di backup e per comodità d'uso. Questo è esattamente il modo in cui arriviamo al fatto ben noto che ha sempre la precedenza su entrambi .equals()
e .hashCode()
se intendete confrontare effettivamente gli oggetti o memorizzarli nell'hash. Sostituire solo uno di essi senza l'altro è un buon modo per rovinare il codice (con risultati di confronto malvagi o valori di collisione follemente elevati) - e aggirarlo è una fonte di costante confusione ed errori per i principianti (cerca SO per vedere per te) e fastidio costante a quelli più stagionati.
Inoltre, nota che sebbene C # gestisca in modo un po 'meglio eguali e hashcode, Eric Lippert stesso afferma di aver fatto quasi lo stesso errore con C # che Sun ha fatto con Java anni prima dell'inizio di C # :
Ma perché dovrebbe essere il caso che ogni oggetto dovrebbe essere in grado di eseguire l'hash per l'inserimento in una tabella hash? Sembra una cosa strana richiedere che ogni oggetto sia in grado di fare. Penso che se stessimo riprogettando il sistema dei tipi da zero oggi, l'hashing potrebbe essere fatto diversamente, forse con IHashable
un'interfaccia. Ma quando è stato progettato il sistema di tipi CLR non c'erano tipi generici e quindi una tabella hash per scopi generici doveva essere in grado di memorizzare qualsiasi oggetto.
1 ovviamente, Object#hashCode
può ancora scontrarsi, ma ci vuole un po 'di sforzo per farlo, vedi: http://bugs.java.com/bugdatabase/view_bug.do?bug_id=6809470 e rapporti sui bug collegati per i dettagli; /programming/1381060/hashcode-uniqueness/1381114#1381114 tratta questo argomento in modo più approfondito.
Person
implementare l'attesoequals
e ilhashCode
comportamento. Avresti quindi unHashMap<PersonWrapper, V>
. Questo è un esempio in cui un approccio puro-OOP non è elegante: non tutte le operazioni su un oggetto hanno senso come metodo di quell'oggetto. Intero JavaObject
tipo è un amalgama di diverse responsabilità - solo lagetClass
,finalize
etoString
metodi sembrano lontanamente giustificabile dalle best practice di oggi.