Java equals() et hashCode()

Java equals() et les méthodes hashCode() sont présentes dans la classe Object. Ainsi, chaque classe Java obtient l’implémentation par défaut de equals() et hashCode(). Dans ce post, nous examinerons en détail les méthodes java equals() et hashCode().

Java equals()

La classe Object définit la méthode equals() comme ceci :

public boolean equals(Object obj) {
        return (this == obj);
}

Selon la documentation Java de la méthode equals(), toute implémentation doit suivre les principes suivants :

  • Pour tout objet x, x.equals(x) devrait renvoyer true.
  • Pour tout objet x et y, x.equals(y) devrait renvoyer true si et seulement si y.equals(x) renvoie true.
  • Pour plusieurs objets x, y et z, si x.equals(y) renvoie true et y.equals(z) renvoie true, alors x.equals(z) devrait renvoyer true.
  • De multiples invocations de x.equals(y) devraient renvoyer le même résultat, à moins que l’une des propriétés de l’objet ne soit modifiée et utilisée dans l’implémentation de equals().
  • L’implémentation de la méthode equals() de la classe Object ne renvoie true que lorsque les deux références pointent vers le même objet.

Java hashCode()

Le hashCode() de l’objet Java est une méthode native qui retourne la valeur de hachage entière de l’objet. Le contrat général de la méthode hashCode() est le suivant :

  • Plusieurs invocations de hashCode() devraient retourner la même valeur entière, sauf si la propriété de l’objet utilisée dans la méthode equals() est modifiée.
  • La valeur du code de hachage d’un objet peut changer lors de multiples exécutions de la même application.
  • Si deux objets sont égaux selon la méthode equals(), alors leur code de hachage doit être identique.
  • Si deux objets sont inégaux selon la méthode equals(), leurs codes de hachage ne sont pas obligés d’être différents. Leur valeur de code de hachage peut être égale ou non.

Importance de la méthode equals() et hashCode()

Les méthodes hashCode() et equals() de Java sont utilisées dans les implémentations de table de hachage en Java pour stocker et récupérer des données. Je l’ai expliqué en détail à Comment fonctionne HashMap en Java? L’implémentation de equals() et hashCode() devrait suivre ces règles.

  • Si o1.equals(o2), alors o1.hashCode() == o2.hashCode() devrait toujours être true.
  • Si o1.hashCode() == o2.hashCode est vrai, cela ne signifie pas que o1.equals(o2) sera vrai.

Quand faut-il remplacer les méthodes equals() et hashCode()?

Lorsque nous remplaçons la méthode equals(), il est presque nécessaire de remplacer également la méthode hashCode() afin que leur contrat ne soit pas violé par notre implémentation. Notez que votre programme ne générera aucune exception si le contrat equals() et hashCode() est violé. Si vous n’avez pas l’intention d’utiliser la classe comme clé de table de hachage, cela ne posera aucun problème. Cependant, si vous prévoyez d’utiliser une classe comme clé de table de hachage, il est impératif de remplacer à la fois les méthodes equals() et hashCode(). Voyons ce qui se passe lorsque nous nous fions à l’implémentation par défaut des méthodes equals() et hashCode() et utilisons une classe personnalisée comme clé de HashMap.

package com.journaldev.java;

public class DataKey {

	private String name;
	private int id;

        // Méthodes getter et setter

	@Override
	public String toString() {
		return "DataKey [name=" + name + ", id=" + id + "]";
	}

}
package com.journaldev.java;

import java.util.HashMap;
import java.util.Map;

public class HashingTest {

	public static void main(String[] args) {
		Map<DataKey, Integer> hm = getAllData();

		DataKey dk = new DataKey();
		dk.setId(1);
		dk.setName("Pankaj");
		System.out.println(dk.hashCode());

		Integer value = hm.get(dk);

		System.out.println(value);

	}

	private static Map<DataKey, Integer> getAllData() {
		Map<DataKey, Integer> hm = new HashMap<>();

		DataKey dk = new DataKey();
		dk.setId(1);
		dk.setName("Pankaj");
		System.out.println(dk.hashCode());

		hm.put(dk, 10);

		return hm;
	}

}

Lorsque nous exécutons le programme ci-dessus, il affichera null. C’est parce que la méthode hashCode() de l’objet est utilisée pour trouver le compartiment où rechercher la clé. Comme nous n’avons pas accès aux clés de HashMap et que nous recréons la clé pour récupérer les données, vous remarquerez que les valeurs de hachage des deux objets sont différentes et donc la valeur n’est pas trouvée.

Mise en œuvre des méthodes equals() et hashCode()

Nous pouvons définir notre propre implémentation des méthodes equals() et hashCode(), mais si nous ne les implémentons pas avec soin, cela peut entraîner des problèmes étranges à l’exécution. Heureusement, la plupart des environnements de développement intégrés de nos jours offrent des moyens de les implémenter automatiquement, et si nécessaire, nous pouvons les modifier selon nos besoins. Nous pouvons utiliser Eclipse pour générer automatiquement les méthodes equals() et hashCode(). Voici les implémentations auto-générées des méthodes equals() et hashCode().

@Override
public int hashCode() {
	final int prime = 31;
	int result = 1;
	result = prime * result + id;
	result = prime * result + ((name == null) ? 0 : name.hashCode());
	return result;
}

@Override
public boolean equals(Object obj) {
	if (this == obj)
		return true;
	if (obj == null)
		return false;
	if (getClass() != obj.getClass())
		return false;
	DataKey other = (DataKey) obj;
	if (id != other.id)
		return false;
	if (name == null) {
		if (other.name != null)
			return false;
	} else if (!name.equals(other.name))
		return false;
	return true;
}

Notez que les méthodes equals() et hashCode() utilisent les mêmes champs pour les calculs, afin que leur contrat reste valide. Si vous exécutez à nouveau le programme de test, l’objet sera récupéré à partir de la carte et le programme affichera 10. Nous pouvons également utiliser Project Lombok pour générer automatiquement les implémentations des méthodes equals() et hashCode().

Qu’est-ce qu’une collision de hachage

En termes très simples, les implémentations des tables de hachage Java utilisent la logique suivante pour les opérations get et put.

  1. Identifiez d’abord le « Bucket » à utiliser en utilisant le code de hachage de la « clé ».
  2. S’il n’y a pas d’objets présents dans le seau avec le même code de hachage, ajoutez l’objet pour l’opération put et retournez null pour l’opération get.
  3. Si d’autres objets se trouvent dans le seau avec le même code de hachage, alors la méthode « clé » equals entre en jeu.
    • Si equals() retourne true et qu’il s’agit d’une opération de mise en place, alors la valeur de l’objet est remplacée.
    • Si equals() retourne false et qu’il s’agit d’une opération de mise en place, alors une nouvelle entrée est ajoutée au seau.
    • Si equals() retourne true et qu’il s’agit d’une opération de récupération, alors la valeur de l’objet est renvoyée.
    • Si equals() retourne false et qu’il s’agit d’une opération de récupération, alors null est renvoyé.

L’image ci-dessous montre les éléments d’un seau de HashMap et comment leurs equals() et hashCode() sont liés. Le phénomène lorsqu’il y a collision de hachage pour deux clés est appelé collision de hachage. Si la méthode hashCode() n’est pas implémentée correctement, il y aura un nombre plus élevé de collisions de hachage et les entrées de la carte ne seront pas correctement réparties, ce qui entraînera une lenteur dans les opérations de récupération et de mise en place. C’est la raison de l’utilisation des nombres premiers dans la génération du code de hachage afin que les entrées de la carte soient correctement réparties dans tous les seaux.

Que se passe-t-il si nous n’implémentons ni hashCode() ni equals() ?

Nous avons déjà vu précédemment que si hashCode() n’est pas implémenté, nous ne pourrons pas récupérer la valeur, car HashMap utilise le code hash pour trouver le compartiment où rechercher l’entrée. Si nous utilisons uniquement hashCode() et ne mettons pas en œuvre equals(), la valeur ne sera pas non plus récupérée, car la méthode equals() renverra false.

Meilleures pratiques pour implémenter les méthodes equals() et hashCode()

  • Utilisez les mêmes propriétés dans les implémentations des méthodes equals() et hashCode(), afin que leur contrat ne soit pas violé lorsque toute propriété est mise à jour.
  • Il est préférable d’utiliser des objets immuables comme clé de la table de hachage afin de pouvoir mettre en cache le code hash plutôt que de le calculer à chaque appel. C’est pourquoi String est un bon candidat pour la clé de la table de hachage, car il est immuable et met en cache la valeur du code hash.
  • Implémentez la méthode hashCode() de manière à ce qu’un nombre minimal de collisions de hachage se produisent et que les entrées soient réparties uniformément dans tous les compartiments.

Vous pouvez télécharger le code complet depuis notre référentiel GitHub.

Source:
https://www.digitalocean.com/community/tutorials/java-equals-hashcode