Ce problème est plus apparent lorsque vous avez différentes implémentations d'une interface, et pour les besoins d'une collection particulière, vous ne vous souciez que de la vue au niveau de l'interface des objets. Par exemple, supposons que vous disposiez d'une interface comme celle-ci:
public interface Person {
int getId();
}
La manière habituelle d'implémenter hashcode()
et equals()
d'implémenter des classes aurait du code comme celui-ci dans la equals
méthode:
if (getClass() != other.getClass()) {
return false;
}
Cela provoque des problèmes lorsque vous mélangez des implémentations de Person
dans a HashMap
. Si le HashMap
seul souci de la vue au niveau de l'interface Person
, alors il pourrait se retrouver avec des doublons qui ne diffèrent que par leurs classes d'implémentation.
Vous pouvez faire fonctionner ce cas en utilisant la même equals()
méthode libérale pour toutes les implémentations, mais vous courez le risque de equals()
faire la mauvaise chose dans un contexte différent (comme comparer deux Person
s qui sont soutenus par des enregistrements de base de données avec des numéros de version).
Mon intuition me dit que l'égalité doit être définie par collection au lieu de par classe. Lorsque vous utilisez des collections qui dépendent de la commande, vous pouvez utiliser une méthode personnalisée Comparator
pour choisir la bonne commande dans chaque contexte. Il n'y a pas d'analogue pour les collections basées sur le hachage. Pourquoi est-ce?
Juste pour clarifier, cette question est distincte de " Pourquoi est .compareTo () dans une interface alors que .equals () est dans une classe en Java? " Car elle traite de l'implémentation des collections. compareTo()
et equals()
/ les hashcode()
deux souffrent du problème de l'universalité lors de l'utilisation des collections: vous ne pouvez pas choisir différentes fonctions de comparaison pour différentes collections. Donc, pour les besoins de cette question, la hiérarchie d'héritage d'un objet n'a pas d'importance du tout; tout ce qui compte est de savoir si la fonction de comparaison est définie par objet ou par collection.
la source
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.IEqualityComparer<T>
à une collection basée sur le hachage. Si vous n'en spécifiez pas, il utilise une implémentation par défaut basée surObject.Equals
etObject.GetHashCode()
. 2) L'OMI remplaçantEquals
un type de référence mutable est rarement une bonne idée. De cette façon, l'égalité par défaut est assez stricte, mais vous pouvez utiliser une règle d'égalité plus détendue lorsque vous en avez besoin via une personnalisationIEqualityComparer<T>
.Réponses:
Cette conception est parfois connue sous le nom d '«égalité universelle», c'est la croyance selon laquelle deux choses sont égales ou non est une propriété universelle.
De plus, l'égalité est une propriété de deux objets, mais dans OO, vous appelez toujours une méthode sur un seul objet , et cet objet décide uniquement comment gérer cet appel de méthode. Ainsi, dans une conception comme Java, où l'égalité est une propriété de l'un des deux objets comparés, il n'est même pas possible de garantir certaines propriétés de base de l'égalité telles que la symétrie (
a == b
⇔b == a
), car dans le premier cas, la méthode est sollicitéea
et dans le second cas elle est sollicitéeb
et en raison des principes de base de l'OO, c'est uniquementa
la décision de (dans le premier cas) oub
décision (dans le deuxième cas) de se considérer ou non comme égale à l 'autre. La seule façon de gagner en symétrie est de faire coopérer les deux objets, mais s'ils ne le font pas… pas de chance.Une solution consisterait à faire de l'égalité non pas une propriété d'un objet, mais soit une propriété de deux objets, soit une propriété d'un troisième objet. Cette dernière option résout également le problème de l'égalité universelle, car si vous faites de l'égalité une propriété d'un troisième objet "contexte", alors vous pouvez imaginer avoir des
EqualityComparer
objets différents pour des contextes différents.C'est la conception choisie pour Haskell, par exemple, avec la
Eq
classe de types. C'est également la conception choisie par certaines bibliothèques Scala tierces (ScalaZ, par exemple), mais pas le noyau Scala ou la bibliothèque standard, qui utilise l'égalité universelle pour la compatibilité avec la plate-forme hôte sous-jacente.Il est intéressant de noter que c'est aussi le design choisi avec Java
Comparable
/Comparator
interfaces. Les concepteurs de Java étaient clairement conscients du problème, mais pour une raison quelconque, ils ne l'ont résolu que pour la commande, mais pas pour l'égalité (ou le hachage).Donc, quant à la question
la réponse est "je ne sais pas". De toute évidence, les concepteurs de Java étaient conscients du problème, comme en témoigne l'existence de
Comparator
, mais ils ne pensaient évidemment pas que c'était un problème d'égalité et de hachage. D'autres langues et bibliothèques font des choix différents.la source
The methods equals and hashCode are declared for the benefit of hashtables such as java.util.Hashtable
c'est-à-dire les deuxequals
ethashCode
ont été introduits commeObject
méthodes par les développeurs Java uniquement pour le plaisir deHashtable
- il n'y a aucune notion d'UE ou quoi que ce soit de silimar dans la spécification, et la citation est assez claire pour moi; sans leHashtable
,equals
aurait probablement été dans une interface commeComparable
. En tant que tel, même si je pensais autrefois que votre réponse était correcte, je considère maintenant qu'elle n'est pas étayée.clone
- c'était à l'origine un opérateur , pas une méthode (voir Oak Language Specification), citation: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)
- les trois opérateurs de type mot clé étaientinstanceof new clone
(section 8.1, opérateurs). Je suppose que c'est la vraie raison (historique) duclone
/Cloneable
mess -Cloneable
c'était simplement une invention plus tardive, et leclone
code existant était mis à niveau avec.La vraie réponse à
est, avec l'aimable autorisation de Josh Bloch :
Le problème réside uniquement dans l'histoire de Java, comme avec d' autres questions similaires, par exemple
.clone()
vsCloneable
.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:
Hashtable
,.hashCode()
&.equals()
ont été implémentés dans JDK 1.0, ( Hashtable )Comparable
/ aComparator
été introduit dans JDK 1.2 ( comparable ),Maintenant, il suit:
.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 mondeObject
les avait, et ils avaient y rester physiquement pour assurer également la compatibilité du code compilé (JVM) - et l'ajout d'une interface explicite à chaqueObject
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),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:(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 directeHashtable
-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 chaqueObject
entréeHashtable
devrait implémenter explicitement une interface (et les interfaces n'en étaient qu'à leurs débuts à l'époque ... pasComparable
encore même!) , ce qui en fait un moyen de dissuasion de l'utiliser pour beaucoup - ouObject
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 uneequals()
méthode distincte - il était donc tout à fait évident qu'ilequals()
fallait également y mettreObject
.Étant donné que les défaut mises en œuvre de ces méthodes sur valide
a
etb
Object
s sont essentiellement inutiles en étant redondants ( ce quia.equals(b)
égale àa==b
eta.hashCode() == b.hashCode()
égale à peu près àa==b
aussi, à moins quehashCode
et / ouequals
est surchargé, ou vous GC des centaines de milliers deObject
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 # :
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.la source
Object
la conception de; l'habilité était.