La vraie réponse à
pourquoi y a-t-il une Comparator
interface mais pas Hasher
et Equator
?
est, avec l'aimable autorisation de Josh Bloch :
Les API Java d'origine ont été réalisées très rapidement dans un délai serré pour respecter une fenêtre de marché de fermeture. L'équipe Java d'origine a fait un travail incroyable, mais toutes les API ne sont pas parfaites.
Le problème réside uniquement dans l'histoire de Java, comme avec d' autres questions similaires, par exemple .clone()
vs Cloneable
.
tl; dr
c'est principalement pour des raisons historiques; le comportement / abstraction actuel a été introduit dans JDK 1.0 et n'a pas été corrigé plus tard car il était pratiquement impossible de le faire avec le maintien de la compatibilité du code en arrière.
Tout d'abord, résumons quelques faits Java bien connus:
- Java, du tout début à nos jours, était fièrement rétrocompatible, nécessitant que les API héritées soient toujours prises en charge dans les versions plus récentes,
- en tant que tel, presque chaque construction de langage introduite avec JDK 1.0 a vécu jusqu'à nos jours,
Hashtable
, .hashCode()
& .equals()
ont été implémentés dans JDK 1.0, ( Hashtable )
Comparable
/ a Comparator
été introduit dans JDK 1.2 ( comparable ),
Maintenant, il suit:
- il était pratiquement impossible et insensé de moderniser
.hashCode()
et .equals()
d'interfaces distinctes tout en conservant la compatibilité descendante après que les gens se soient rendus compte qu'il y avait de meilleures abstractions que de les mettre en superobjet, parce que par exemple, chaque programmeur Java par 1.2 savait que tout le monde Object
les avait, et ils avaient y rester physiquement pour assurer également la compatibilité du code compilé (JVM) - et l'ajout d'une interface explicite à chaque Object
sous-classe qui les a réellement implémentées rendrait ce désordre égal (sic!) à Clonable
un ( Bloch explique pourquoi Cloneable craint , également abordé par exemple dans EJ 2nd et bien d'autres endroits, dont SO),
- ils les ont simplement laissés là pour que la future génération ait une source constante de WTF.
Maintenant, vous pouvez demander "qu'est-ce qui Hashtable
a tout cela"?
La réponse est: hashCode()
/ equals()
contrat et compétences en conception de langage pas si bonnes des principaux développeurs Java en 1995/1996.
Citation de Java 1.0 Language Spec, datée de 1996 - 4.3.2 The Class Object
, p.41:
Les méthodes equals
et hashCode
sont déclarées au profit de tables de hachage telles que java.util.Hashtable
(§21.7). La méthode equals définit une notion d'égalité d'objet, qui est basée sur une comparaison de valeur et non de référence.
(notez que cette déclaration exacte a été modifiée dans les versions ultérieures, à savoir, citation:, The method hashCode is very useful, together with the method equals, in hashtables such as java.util.HashMap.
rendant impossible la connexion directe Hashtable
- hashCode
- equals
sans lire l'historique JLS!)
L'équipe Java a décidé qu'ils voulaient une bonne collection de style dictionnaire, et ils ont créé Hashtable
(bonne idée jusqu'à présent), mais ils voulaient que le programmeur puisse l'utiliser avec le moins de code / courbe d'apprentissage possible (oups! Problèmes entrants!) - et, comme il n'y avait pas encore de génériques [c'est JDK 1.0 après tout], cela signifierait que chaque Object
entrée Hashtable
devrait implémenter explicitement une interface (et les interfaces n'en étaient qu'à leurs débuts à l'époque ... pas Comparable
encore même!) , ce qui en fait un moyen de dissuasion de l'utiliser pour beaucoup - ou Object
devrait implicitement implémenter une méthode de hachage.
De toute évidence, ils ont opté pour la solution 2, pour les raisons décrites ci-dessus. Ouais, maintenant nous savons qu'ils avaient tort. ... il est facile d'être intelligent avec le recul. glousser
Maintenant, il hashCode()
faut que chaque objet l'ayant doit avoir une equals()
méthode distincte - il était donc tout à fait évident qu'il equals()
fallait également y mettre Object
.
Étant donné que les défaut mises en œuvre de ces méthodes sur valide a
et b
Object
s sont essentiellement inutiles en étant redondants ( ce qui a.equals(b)
égale à a==b
et a.hashCode() == b.hashCode()
égale à peu près à a==b
aussi, à moins que hashCode
et / ou equals
est surchargé, ou vous GC des centaines de milliers de Object
s au cours du cycle de vie de votre application 1 ) , il est sûr de dire qu'ils ont été fournis principalement à titre de mesure de sauvegarde et pour des raisons de commodité d'utilisation. C'est exactement comme cela que nous arrivons au fait bien connu qui remplace toujours les deux .equals()
et .hashCode()
si vous avez l'intention de comparer les objets ou de les stocker par hachage. Surcharger un seul d'entre eux sans l'autre est un bon moyen de visser votre code (en comparant les résultats ou les valeurs de collision incroyablement élevées) - et de le contourner est une source de confusion et d'erreurs constantes pour les débutants (recherchez SO pour voir pour vous-même) et une nuisance constante pour les plus aguerris.
Notez également que bien que C # traite les égaux et le code de hachage de manière un peu meilleure, Eric Lippert lui-même déclare qu'ils ont fait presque la même erreur avec C # que Sun a fait avec Java des années avant la création de C # :
Mais pourquoi devrait-il être le cas que chaque objet devrait pouvoir se hacher pour être inséré dans une table de hachage? Cela semble étrange d'exiger que chaque objet soit capable de le faire. Je pense que si nous repensions le système de type à partir de zéro aujourd'hui, le hachage pourrait être fait différemment, peut-être avec une IHashable
interface. Mais lorsque le système de type CLR a été conçu, il n'y avait pas de types génériques et donc une table de hachage à usage général devait être en mesure de stocker n'importe quel objet.
1 bien sûr, Object#hashCode
peut encore entrer en collision, mais cela demande un peu d'effort, voir: http://bugs.java.com/bugdatabase/view_bug.do?bug_id=6809470 et les rapports de bogues liés pour plus de détails; /programming/1381060/hashcode-uniqueness/1381114#1381114 couvre ce sujet plus en profondeur.
Person
implémenter le comportement attenduequals
ethashCode
. Vous auriez alors unHashMap<PersonWrapper, V>
. C'est un exemple où une approche pure-POO n'est pas élégante: toutes les opérations sur un objet n'ont pas de sens en tant que méthode de cet objet. LeObject
type entier de Java est un amalgame de responsabilités différentes - seules les méthodesgetClass
,finalize
ettoString
semblent à distance justifiables par les meilleures pratiques d'aujourd'hui.