La vraie réponse à
pourquoi y a-t-il une Comparatorinterface mais pas Hasheret 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 Objectles avait, et ils avaient y rester physiquement pour assurer également la compatibilité du code compilé (JVM) - et l'ajout d'une interface explicite à chaque Objectsous-classe qui les a réellement implémentées rendrait ce désordre égal (sic!) à Clonableun ( 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 Hashtablea 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 equalset hashCodesont 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- equalssans 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 Hashtabledevrait implémenter explicitement une interface (et les interfaces n'en étaient qu'à leurs débuts à l'époque ... pas Comparableencore même!) , ce qui en fait un moyen de dissuasion de l'utiliser pour beaucoup - ou Objectdevrait 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 aet b Objects sont essentiellement inutiles en étant redondants ( ce qui a.equals(b) égale à a==bet a.hashCode() == b.hashCode() égale à peu près à a==baussi, à moins que hashCodeet / ou equalsest surchargé, ou vous GC des centaines de milliers de Objects 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 IHashableinterface. 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#hashCodepeut 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.
Personimplémenter le comportement attenduequalsethashCode. 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. LeObjecttype entier de Java est un amalgame de responsabilités différentes - seules les méthodesgetClass,finalizeettoStringsemblent à distance justifiables par les meilleures pratiques d'aujourd'hui.