Dans le développement Java, comprendre et bien utiliser les méthodes equals()
et hashCode()
est essentiel pour écrire un code fiable et éviter des erreurs lors de l’utilisation des collections telles que HashMap
ou HashSet
.
Ces deux méthodes jouent un rôle clé dans la gestion des objets, notamment lorsqu’il s’agit de les comparer et de les stocker dans des structures de données basées sur le hachage. Pourtant, beaucoup de développeurs les comprennent mal et ne les implémentent pas correctement, ce qui peut mener à des comportements imprévisibles dans les applications.
Cet article vous permettra de comprendre, au travers d’exemples concrets, l’importance de la réimplémentation de ces méthodes, dans la gestion des objets en Java.
Comparer des objets en Java
Lorsque nous comparons deux objets en Java, nous cherchons à savoir si ils sont égaux ou non. En d’autre termes, nous cherchons à savoir si ils sont sémantiquement équivalents et qu’ils représentent donc la même entité.
Exemples concrets
Considérons deux objets Java différents qui représentent une même personne nommée “James Gosling”, il s’ait de la même personne, elle a donc le même prénom, le même nom et le même numéro de sécurité sociale. Nous attendons de notre programme qu’il reconnaisse que ces deux objets représentent la même entité dans le monde réel. Pour cela, nous ne pouvons pas utiliser la méthode equals()
de la classe Object
, nous sommes obligés de la redéfinir, pour qu’elle s’applique de manière correcte à notre objet Person
public class Person {
private String firstName;
private String lastName;
private String socialSecurityNumber;
public Person(String firstName, String lastName, String socialSecurityNumber) {
this.firstName = firstName;
this.lastName = lastName;
this.socialSecurityNumber = socialSecurityNumber;
}
}
Importance de la réimplémentation de equals()
Sans réimplémenter la méthode equals()
, c’est la méthode equals()
de la classe Object
qui sera appelée et exécutée, cette dernière utilise l’opérateur ==
pour définir l’égalité de deux objets.
Cet opérateur, sert à comparer des types primitifs, dans le cas des objets, il va comparer les références mémoires de ces derniers.
Sans réimplémenter equals()
, on ne vérifie dont uniquement que deux variables pointent vers la même adresse mémoire. Cette comparaison ne va donc fonctionner que dans le cas particulier suivant ou nous avons deux variables qui pointent vers le même objet en mémoire
Person person1 = new Person("James", "Gosling", "123-45-6789");
Person person2 = person1;
System.out.println("Comparaison avec == : " + (person1 == person2)); // retournera true
System.out.println("Comparaison avec equals : " + person1.equals(person2)); // retournera true
Dans l’exemple ci-dessous en revanche, person1
et person2
sont des objets différents en mémoire (leur adresse mémoire est différente), mais ils ont les mêmes attributs et représentent donc la même personne dans le monde réel. Une comparaison avec ==
ne pourra donc plus fonctionner. Notre programme nous indiquera avoir deux objets différents, alors qu’ils représentent pourtant la même entité.
Person person1 = new Person("James", "Gosling", "123-45-6789");
Person person2 = new Person("James", "Gosling", "123-45-6789");
System.out.println("Comparaison avec == : " + (person1 == person2)); // retournera false
System.out.println("Comparaison avec equals() : " + person1.equals(person2)); // retournera true
Nous devons donc redéfinir la méthode equals()
pour que la comparaison se fasse sur les attributs de l’objet plutôt que sur l’adresse mémoire de ces derniers.
public class Person {
private String firstName;
private String lastName;
private String socialSecurityNumber;
public Person(String firstName, String lastName, String socialSecurityNumber) {
this.firstName = firstName;
this.lastName = lastName;
this.socialSecurityNumber = socialSecurityNumber;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return Objects.equals(firstName, person.firstName) && Objects.equals(lastName, person.lastName) && Objects.equals(socialSecurityNumber, person.socialSecurityNumber);
}
}
Dans cet article, nous n’expliquerons pas en détail les bonnes pratiques pour la création des méthodes equals()
et hashcode()
, les IDE ou bibliothèques comme Lombok, proposent des solutions pour générer automatiquement ces dernières, ce qui évite de devoir les écrire manuellement et réduit donc le risque d’erreurs. Vous trouverez plus de détails dans la documentation officielle, si cela vous intéresse.
Réimplémentation de hashCode()
La méthode hashCode()
est étroitement liée à la méthode equals()
. En Java, chaque objet a un code de hachage (un nombre entier qui le représente) qui sera utilisé pour le stocker efficacement dans des structures de données basées sur le hachage, comme HashSet
et HashMap
.
Le contrat de la méthode hashcode()
stipule que :
Si deux objets sont égaux selon la méthode
equals()
, ils doivent avoir le même code de hachage.
Ne pas réimplémenter hashCode()
conformément à equals()
peut entraîner des comportements imprévisibles. Par exemple, si deux objets sont considérés égaux avec equals()
mais ont des codes de hachage différents, une HashMap
par exemple les traitera comme des entrées distinctes, ce qui pourrait conduire à des doublons inattendus ou à l’échec de la recherche d’une clé présente.
En effet, dans les collections basées sur le hachage (comme HashMap
, HashSet
, etc.), il est essentiel que les méthodes equals()
et hashCode()
soient correctement implémentées de manière cohérente. Ces collections utilisent des codes de hachage pour organiser les éléments et permettre une recherche rapide.
Fonctionnement d’une collection (structure de données) basée sur le hachage :
Lorsqu’un objet est ajouté à une collection, son code de hachage (généré par hashCode()
) est utilisé pour déterminer dans quel compartiment (ou “bucket”) il sera stocké.
Si plusieurs objets ont le même code de hachage, ils sont placés dans le même compartiment, et la méthode equals()
est utilisée pour différencier les objets.
Problèmes potentiels :
Si la méthode hashCode()
n’est pas implémentée de manière cohérente avec equals()
, deux objets qui sont égaux selon equals()
peuvent avoir des codes de hachage différents. En conséquence, ils seront envoyés dans des compartiments différents, et la collection ne pourra pas les comparer correctement, ce qui peut entraîner un mauvais comportement lors des opérations de recherche ou de suppression.
Échec de la recherche : Lors d’une recherche d’un élément dans une collection par exemple, le code de hachage de l’élément donné va être utilisé pour le retrouver dans la collection. La recherche s’effectuera uniquement dans le compartiment (bucket) où l’objet est censé se trouver. Si un objet recherché est égal à un autre déjà présent mais avec un code de hachage différent, la recherche ne sera pas faite dans le bon compartiment, et elle échouera, même si l’objet est présent car il se trouve dans un autre compartiment.
Doublons inattendus : Si deux objets qui sont égaux selon equals()
ont des codes de hachage différents, ils seront stockés dans des compartiments différents. Cela signifie que la collection ne pourra pas les comparer entre eux avec equals()
, car ils ne se trouvent pas dans le même compartiment. Ainsi, la collection les considérera comme des objets distincts, ce qui entrainera des doublons inattendus.
Le Cas des Hashcodes Similaires
Imaginez que vous êtes devant un hôtel luxueux avec un service de valet. Le système fonctionne de manière organisée : les valets rangent les voitures des clients dans le parking en fonction de leur couleur et de leur marque, afin de pouvoir retrouver chaque voiture rapidement sans devoir chercher dans tout le parking. Cela ressemble à la manière dont un hashCode()
fonctionne en programmation : il permet de classer et d’organiser les objets dans des “buckets” spécifiques, comme les valets qui attribuent des places de parking selon des critères bien définis.
Sur notre parking, il est tout à fait possible que deux voitures rouges de la même marque soient garées dans la même zone, mais avec des plaques d’immatriculation différentes. Elles auront le même hashcode (elles sont rouges et de la même marque, donc garées dans la même zone, même bucket), mais elles ne seront pas considérées comme égales, car leurs plaques sont différentes.
En programmation, il en va de même : deux objets qui ne sont pas égaux peuvent avoir le même hashcode. Cela se produit parce que le hashcode ne garantit pas l’unicité des objets, mais permet une organisation rapide des objets en buckets pour faciliter les recherches.
public class Car {
private String color;
private String brand;
private String licensePlate;
@Override
public int hashCode() {
return Objects.hash(color, brand);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Car car = (Car) o;
return color.equals(car.color) &&
brand.equals(car.brand) &&
licensePlate.equals(car.licensePlate);
}
}
Dans cet exemple, deux objets Car avec la même couleur et marque, auront le même hashcode (ils seront donc garés dans la même zone du parking), on les distinguera via un nombre plus importants d’attributs, notamment la chaîne de caractères indiquée sur leur plaque d’immatriculation (appel à la méthode equals()
) .
Conclusion
Comprendre le rôle des méthodes equals()
et hashcode()
est crucial pour travailler efficacement avec les collections en Java. Le hashcode aide à répartir les objets dans des buckets pour une recherche rapide, tandis que la méthode equals()
vérifie l’égalité exacte des objets. Ces deux méthodes sont indissociables et indispensables.
Pour aller plus loin :
- Documentation officielle de Java :
Object.equals()
etObject.hashCode()
– Cette documentation explique en détail le contrat entreequals()
ethashCode()
. - Collections Framework en Java – Une ressource utile pour comprendre comment les collections Java, comme
HashSet
etHashMap
, utilisenthashCode()
etequals()
. - Les bonnes pratiques d’implémentation de
equals()
ethashCode()
– Un article complémentaire sur Baeldung pour approfondir les différentes façons d’implémenter ces méthodes de manière optimale.