Les méthodes HashCode () et Equals () peuvent être considérées comme une caractéristique majeure de l'orientation complètement orientée objet de Java. Il facilite notre programmation et apporte également beaucoup de dangers. Dans cet article, nous discuterons de la façon de comprendre et d'utiliser correctement ces deux méthodes.
Si vous décidez de réécrire la méthode equals (), vous devez être clair sur les risques apportés en le faisant et vous assurer que vous pouvez écrire une méthode Equals () robuste. Une chose que vous devez noter est qu'après la réécriture equals (), vous devez réécrire la méthode hashcode (). Les raisons spécifiques seront expliquées plus tard.
Jetons d'abord un coup d'œil à la description de la méthode equals () dans Javase 7 Spécification:
・ Il est réflexif: pour toute valeur de référence non nulle x, x.equals(x) doit retourner true .
・ Il est symétrique: pour toute valeur de référence non nul, x et y, x.equals(y) doivent retourner true si et seulement si y.equals(x) renvoie true .
・ Il est transitif: pour toutes les valeurs de référence non nuls x, y, et z, si x.equals(y) renvoie true et y.equals(z) renvoie true , alors x.equals(z) devrait retourner true.
・ Il est cohérent: pour toute valeur de référence non nul x et y , plusieurs invocations de x.equals(y) renvoie systématiquement true ou renvoyez systématiquement false , à condition qu'aucune information utilisée dans les comparaisons égaux sur les objets ne soit modifiée.
・ Pour toute valeur de référence non nul x, x.equals(null) doit renvoyer false .
Ce passage utilise beaucoup de numérologie en mathématiques discrètes. Permettez-moi de donner une brève explication:
1. Réflexivité: A. Equals (a) doit revenir vrai.
2. Symétrie: si A. Equals (b) revient vrai, alors B.equals (a) doit également revenir vrai.
3. Transmission: Si A. Equals (b) est vrai et B.Equals (c) est vrai, alors a.équal (c) doit également être vrai. Pour le dire franchement, a = b, b = c, puis a = C.
4. Cohérence: Tant que l'état des objets A et B ne change pas, A. Equals (b) doit toujours revenir vrai.
5. A. Equals (null) pour retourner false.
Je crois que tant que les gens qui ne sont pas professionnels en mathématiques n'appelleront pas les choses ci-dessus. Dans l'application réelle, nous n'avons qu'à réécrire la méthode equals () en fonction de certaines étapes. Pour plus de commodité de l'explication, nous définissons d'abord une classe de programmeur (codeur):
classe CODER {nom de chaîne privée; Âge privé; // getters et setters}Ce que nous voulons, c'est que si les noms et les âges des deux objets programmeurs sont les mêmes, alors nous pensons que ces deux programmeurs sont les mêmes. À l'heure actuelle, nous devons réécrire sa méthode equals (). Étant donné que la valeur par défaut est égale () détermine réellement si deux références pointent vers le même objet intrinsèque, il est équivalent à ==. Lors de la réécriture, suivez les trois étapes suivantes:
1. Déterminez s'il est égal à vous-même.
if (autre == ceci) renvoie true;
2. Utilisez l'opérateur d'instance pour déterminer si l'autre est un objet de codeur de type.
if (! (Autre instance de codeur)) renvoie false;
3. Comparez les domaines de données, le nom et l'âge que vous personnalisez dans la classe Coders, et vous ne devez pas en manquer un.
Codeur o = (codeur) Autre; return o.name.equals (name) && o.age == Âge;
Voyant cela, quelqu'un peut demander, il y a un casting à l'étape 3. Si quelqu'un passe un objet de classe entière dans ce domaine égal, je vais-il lancer une classcastException? Cette inquiétude est en fait redondante. Parce que nous avons porté le jugement de l'instance dans la deuxième étape, si d'autres sont un objet non coder, ou même que d'autres sont nuls, alors False sera directement renvoyé à cette étape, afin que le code suivant n'ait pas l'occasion d'être exécuté.
Les trois étapes ci-dessus sont également les étapes recommandées dans <Effective Java>, qui peuvent essentiellement garantir qu'il n'y a pas d'erreur.
Dans la spécification Javase 7,
"Notez qu'il est généralement nécessaire de remplacer la méthode HashCode chaque fois que cette méthode (égal) est remplacée, afin de maintenir le contrat général pour la méthode HashCode, qui indique que les objets égaux doivent avoir des codes de hachage égaux."
Si vous réécrivez la méthode equals (), n'oubliez pas de réécrire la méthode HashCode (). Nous avons appris les tables de hachage dans les cours de structure de données informatiques universitaires. La méthode HashCode () sert la table de hachage.
Lorsque nous utilisons une classe de collection qui commence par un hash comme le hash, tel que HashMap et HashSet, HashCode () sera appelé implicitement pour créer une relation de cartographie de hachage. Nous expliquerons cela plus tard. Ici, nous nous concentrerons d'abord sur l'écriture de la méthode HashCode ().
<Efficace Java> fournit une méthode d'écriture qui peut éviter les conflits de hachage dans la plus grande mesure, mais je pense personnellement qu'il n'est pas nécessaire de faire tant de problèmes pour les applications générales. Si vous avez besoin de stocker des dizaines de milliers ou des millions d'objets dans votre application, vous devez suivre strictement les méthodes données dans le livre. Si vous écrivez une petite et moyenne taille, les principes suivants sont suffisants:
Il est nécessaire de s'assurer que tous les membres de l'objet Coders peuvent être reflétés dans HashCode.
Pour cet exemple, nous pouvons écrire ceci:
@Override public int hashcode () {int result = 17; résultat = résultat * 31 + name.hashcode (); Résultat = résultat * 31 + âge; Résultat de retour; }Où Int Result = 17, vous pouvez également le changer en 20, 50, etc. Voyant cela, j'étais soudainement curieux et je voulais voir comment la méthode HashCode () dans la classe String est implémentée. Vérifiez la documentation et sachez:
"Renvoie un code de hachage pour cette chaîne. Le code de hachage pour un objet String est calculé comme
s [0] * 31 ^ (n-1) + s [1] * 31 ^ (n-2) + ... + s [n-1]
En utilisant int arithmétique, où S [i] est le ième caractère de la chaîne, n est la longueur de la chaîne et ^ indique l'exponentiation. (La valeur de hachage de la chaîne vide est nulle.) "
Calculez le code ASCII de chaque caractère à la puissance N - 1, puis ajoutez-le. On peut voir que le soleil est très strict dans la mise en œuvre de HashCode. Cela peut éviter le même code de hash dans les deux chaînes différentes dans la plus grande mesure.
Le concept de seau est référencé dans la mise en œuvre de la table de hachage d'Oracle. Comme indiqué dans la figure ci-dessous:
Comme le montre la figure ci-dessus, la table de hachage avec seau est à peu près équivalente à une combinaison d'une table de hachage et d'une liste liée. Autrement dit, une liste liée sera accrochée à chaque seau, et chaque nœud de la liste liée sera utilisé pour stocker des objets. Java utilise la méthode HashCode () pour déterminer quel godet un objet doit être localisé, puis les recherche dans la liste liée correspondante. Idéalement, si votre méthode HashCode () est écrite suffisamment robuste, chaque godet n'aura qu'un seul nœud, qui atteindra la complexité temporelle de niveau constant de l'opération de recherche. Autrement dit, peu importe dans quel morceau de mémoire dans votre objet est placé, je peux immédiatement localiser la zone via HashCode () sans traverser et rechercher du début à la fin. Il s'agit également de l'application principale des tables de hachage.
comme:
Lorsque nous appelons la méthode de put (objet o) de HashSet, nous le localiserons d'abord dans le seau correspondant en fonction de la valeur de retour d'O.HashCode (). S'il n'y a pas de nœuds dans le seau, alors mettez O ici. S'il y a déjà des nœuds, accrochez-vous à la fin de la liste liée. De même, lorsque l'appel contient (objet O), Java localisera le godet correspondant via la valeur de retour de HashCode (), puis appellera la méthode equals () à son tour aux nœuds dans la liste liée correspondante pour déterminer si l'objet dans le nœud est l'objet que vous souhaitez.
Utilisons un exemple pour vivre ce processus:
Créons d'abord deux nouveaux objets de codeur:
Codeur C1 = nouveau codeur ("Bruce", 10); Codeur C2 = nouveau codeur ("Bruce", 10);Supposons que nous avons réécrit la méthode equals () de codeur sans réécrire la méthode hashcode ():
@Override public booléen est égal (objet autre) {System.out.println ("égal à la méthode invoquée!"); if (autre == ceci) renvoie true; if (! (Autre instance de codeur)) renvoie false; Codeur o = (codeur) Autre; return o.name.equals (name) && o.age == Âge; }Ensuite, nous construisons un hashset et mettons l'objet C1 dans l'ensemble: nous
Set <Coded> set = new HashSet <Deder> (); set.add (C1);
Exécuter à nouveau:
System.out.println (set.Contains (C2));
Nous nous attendons à ce que la méthode contient (C2) renvoie vrai, mais en fait, il renvoie faux.
Le nom et l'âge de C1 et C2 sont les mêmes. Pourquoi est-ce que j'appelle Contient (C2) et retourne false après avoir mis C1 dans un hashset? Il s'agit du HashCode () qui cause des problèmes. Parce que vous n'avez pas réécrit la méthode HashCode (), lorsque HashSet recherche C2, il le cherchera dans différents seaux. Par exemple, si C1 est placé dans le seau 05, il est recherché dans le seau 06 lors de la recherche de C2, donc bien sûr, il ne peut pas être trouvé. Par conséquent, le but de notre réécriture HashCode () est que lorsque A.equals (b) renvoie vrai, le HashCode () de A et B doit renvoyer la même valeur.
Est-ce que je demande à HashCode () de renvoyer une ligne numérique fixe à chaque fois
Quelqu'un pourrait le réécrire comme ceci:
@Override public int hashcode () {return 10; }Si tel est le cas, Hashmap, HashSet et d'autres classes de collecte perdront leur "sens de hachage". Selon les mots de <Effective Java>, la table de hachage dégénère en une liste liée. Si HashCode () renvoie le même numéro à chaque fois, tous les objets seront placés dans le même seau, et chaque fois que vous effectuez une opération de recherche, il traversera la liste liée, qui perdra complètement la fonction du hachage. Il est donc préférable de fournir un HashCode robuste () comme une bonne idée.
Ce qui précède est toute l'introduction détaillée de cet article sur la réécriture des méthodes HashCode () et Equals (). J'espère que ce sera utile à tout le monde. Les amis intéressés peuvent continuer à se référer à d'autres sujets connexes sur ce site. S'il y a des lacunes, veuillez laisser un message pour le signaler. Merci vos amis pour votre soutien pour ce site!