Pourquoi remplacer les méthodes égales et hashcode en Java ?
Source :
Medium Cet article se concentre sur deux méthodes étroitement liées : equals() et hashcode() . Vous apprendrez comment ils interagissent les uns avec les autres et comment les remplacer correctement.
Pourquoi remplaçons-nous la méthode equals() ?
En Java, nous ne pouvons pas surcharger le comportement des opérateurs comme
== ,
+= ,
-+ . Ils travaillent selon un processus donné. Par exemple, considérons le fonctionnement de l' opérateur
== .
Comment fonctionne l'opérateur == ?
Il vérifie si les deux références comparées pointent vers la même instance en mémoire. L' opérateur
== ne sera évalué à vrai que si les deux références représentent la même instance en mémoire. Jetons un coup d'œil à l'exemple de code :
public class Person {
private Integer age;
private String name;
..getters, setters, constructors
}
Disons que dans votre programme vous avez créé deux objets
Person à des endroits différents et que vous souhaitez les comparer.
Person person1 = new Person("Mike", 34);
Person person2 = new Person("Mike", 34);
System.out.println( person1 == person2 ); --> will print false!
D’un point de vue commercial, les deux se ressemblent, n’est-ce pas ? Mais pour la JVM, ce ne sont pas les mêmes. Puisqu’elles sont toutes deux créées à l’aide du mot-clé
new , ces instances sont situées dans des segments de mémoire différents. Par conséquent, l' opérateur
== renverra
false . Mais si nous ne pouvons pas remplacer l' opérateur
== , comment pouvons-nous dire à la JVM que nous voulons que ces deux objets soient traités de la même manière ? C'est là qu'intervient
la méthode .equals() . Vous pouvez remplacer
equals() pour vérifier si certains objets ont les mêmes valeurs pour certains champs afin de les considérer égaux. Vous pouvez choisir les champs à comparer. Si nous disons que deux objets
Person seront identiques seulement s'ils ont le même âge et le même nom, alors l'EDI générera quelque chose comme ceci pour créer automatiquement
equals() :
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return age == person.age &&
name.equals(person.name);
}
Revenons à notre exemple précédent.
Person person1 = new Person("Mike", 34);
Person person2 = new Person("Mike", 34);
System.out.println ( person1 == person2 ); --> will print false!
System.out.println ( person1.equals(person2) ); --> will print true!
Oui, nous ne pouvons pas surcharger l' opérateur
== pour comparer les objets comme nous le souhaitons, mais Java nous offre un autre moyen : la méthode
equals() , que nous pouvons remplacer à notre guise.
Gardez à l'esprit que si nous ne fournissons pas notre version personnalisée de .equals() (également appelée remplacement) dans notre classe, alors le .equals() prédéfini de la classe Object et l' opérateur == se comporteront de la même manière. La méthode par défaut
equals() , héritée de
Object , vérifiera si les deux instances comparées sont identiques en mémoire !
Pourquoi remplaçons-nous la méthode hashCode() ?
Certaines structures de données en Java, telles que
HashSet et
HashMap , stockent leurs éléments en fonction d'une fonction de hachage appliquée à ces éléments. La fonction de hachage est
hashCode() . Si nous avons le choix de remplacer
la méthode .equals() , alors nous devrions également avoir le choix de remplacer
la méthode hashCode() . Il y a une raison à cela. Après tout, l'implémentation par défaut
de hashCode() , héritée de
Object , considère tous les objets en mémoire comme uniques ! Mais revenons à ces structures de données de hachage. Il existe une règle pour ces structures de données.
Un HashSet ne peut pas contenir de valeurs en double et un HashMap ne peut pas contenir de clés en double. Un HashSet est implémenté à l'aide
d'un HashMap de telle sorte que chaque valeur
de HashSet soit stockée sous forme de clé dans
le HashMap . Comment fonctionne
HashMap ?
HashMap est un tableau natif avec plusieurs segments. Chaque segment a une liste chaînée (
linkedList ). Cette liste chaînée stocke nos clés.
HashMap trouve la liste liée correcte pour chaque clé à l'aide de la méthode
hashCode() , puis parcourt tous les éléments de cette
liste liée et applique la méthode
equals() à chacun de ces éléments pour vérifier si cet élément y est contenu.
Les clés en double ne sont pas autorisées. Lorsque nous mettons quelque chose dans
un HashMap , la clé est stockée dans l'une de ces listes chaînées. La liste chaînée dans laquelle cette clé sera stockée est indiquée par le résultat de la méthode
hashCode() pour cette clé. Autrement dit, si
key1.hashCode() donne 4, alors cette
key1 sera stockée dans le 4ème segment du tableau dans
la LinkedList qui y existe . Par défaut, la méthode
hashCode() renvoie des résultats différents pour chaque instance. Si nous avons un
égal par défaut qui se comporte comme
== , traitant toutes les instances en mémoire comme des objets différents, alors il n'y aura pas de problème. Comme vous vous en souvenez peut-être, dans notre exemple précédent, nous avons dit que nous souhaitions que les instances
Person soient considérées comme égales si leurs âges et leurs noms sont identiques.
Person person1 = new Person("Mike", 34);
Person person2 = new Person("Mike", 34);
System.out.println ( person1.equals(person2) ); --> will print true!
Créons maintenant une carte pour stocker ces instances sous forme de clés avec une chaîne spécifique comme paire de valeurs.
Map<Person, String> map = new HashMap();
map.put(person1, "1");
map.put(person2, "2");
Dans la classe
Person , nous n'avons pas remplacé la méthode
hashCode , mais nous avons une méthode
equals remplacée . Étant donné que le
hashCode par défaut donne des résultats différents pour différentes instances Java
de person1.hashCode() et
person2.hashCode() , il y a de fortes chances d'obtenir des résultats différents. Notre carte peut se terminer avec différentes
personnes dans différentes listes chaînées.
Cela va à l'encontre de la logique
de HashMap .
Après tout, un HashMap ne peut pas avoir plusieurs clés identiques ! Le fait est que le
hashCode() par défaut hérité de la classe
Object n'est pas suffisant. Même après avoir remplacé la méthode
equals() de la classe
Person . C'est pourquoi nous devons remplacer la méthode
hashCode() après avoir remplacé la méthode
equals . Maintenant, réparons cela. Nous devons remplacer notre méthode
hashCode() afin qu'elle prenne en compte les mêmes champs que
equals() , à savoir
age et
name .
public class Person {
private Integer age;
private String name;
..getters, setters, constructors
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return age == person.age &&
name.equals(person.name);
}
@Override
public int hashCode() {
int prime = 31;
return prime*Objects.hash(name, age);
}
Dans la méthode
hashCode() , nous avons utilisé une valeur simple (vous pouvez utiliser n'importe quelle autre valeur). Cependant, il est suggéré d’utiliser des nombres premiers pour créer moins de problèmes. Essayons à nouveau de stocker ces clés dans notre
HashMap :
Map<Person, String> map = new HashMap();
map.put(person1, "1");
map.put(person2, "2");
person1.hashCode() et
person2.hashCode() seront identiques. Disons qu'ils sont 0.
Le HashMap ira au segment 0 et
la LinkedList y enregistrera
person1 en tant que clé avec la valeur « 1 ». Dans le deuxième cas, lorsque
HashMap retourne au bucket 0 pour stocker la clé
person2 avec la valeur « 2 », il verra qu'une autre clé égale à celle-ci existe déjà là-bas. De cette façon, la clé précédente sera écrasée. Et seule la
personne clé2 existera dans notre
HashMap . C'est ainsi que nous avons appris le fonctionnement de la règle
HashMap , qui stipule qu'on ne peut pas utiliser plusieurs clés identiques !
Cependant, gardez à l’esprit que des instances inégales peuvent avoir le même hashcode et que les instances égales doivent renvoyer le même hashcode.
GO TO FULL VERSION