Accueil > Non classé > Comment redéfinir correctement la méthode equals() ?

Comment redéfinir correctement la méthode equals() ?

Lorsque l’on crée un POJO, on peut avoir besoin de redéfinir la méthode equals() pour diverses raisons. Par exemple, si l’on veut insérer des objets dans une Collection et pouvoir utiliser la méthode contains(). Ou encore, parce que l’on veut utiliser ces objets comme clé dans une HashMap.

import java.util.ArrayList;
import java.util.List;

public class MyClass {

	public MyClass() {
		List<Person> list = new ArrayList<Person>();
		Person p1 = new Person("toto", 10);
		list.add(p1);

		Person p2 = new Person("toto", 10);

		// imprime false car equals() n'est pas redéfinie
		System.out.println(list.contains(p2));
	}

	private class Person {

		private String name;
		private int age;

		public Person(String name, int age) {
			this.name = name;
			this.age = age;
		}

		public String getName() {
			return name;
		}

		public void setName(String name) {
			this.name = name;
		}

		public int getAge() {
			return age;
		}

		public void setAge(int age) {
			this.age = age;
		}
	}

	public static void main(String s[]) {
		MyClass myClass = new MyClass();
	}
}

L’exemple ci-dessus montre que la méthode contains() ne fonctionne pas comme on le voudrais car la classe Person ne redéfinit pas equals(). Au lieu de comparer un à un les attributs de la classe, equals() va par défaut comparer les références.

Voici la méthode equals() qui permet d’obtenir le résultat attendu :

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

Toutefois, lorsque l’on redéfinit equals(), il y a une règle très importante à respecter :
Si 2 objets sont égaux selon la méthode equals(), alors ils doivent avoir le même hashcode.
Or, dans notre exemple, 2 objets Person égaux selon equals() n’ont pas le même hashcode :

System.out.println(p1.hashCode()); //imprime 1671711
System.out.println(p2.hashCode()); //imprime 11394033

C’est pourquoi il faut toujours redéfinir la méthode hashCode() lorsque l’on redéfinit la méthode equals(). Voici la méthode hashCode() qui correspond à notre exemple :

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

Vous vous demandez peut-être pourquoi cette règle est importante ? Parce que la méthode hashCode() permet d’améliorer grandement les performances des structures de données basées sur des hash comme les Hashtable, les HashMap, et les HashSet. Pour comprendre pourquoi, il faut savoir comment fonctionne une HashMap.

Une HashMap contient en interne un tableau. Et en général, lorsqu’on veut accéder à un élément d’un tableau, on doit savoir à quelle position se situe l’élément dans le tableau. Cette position est toujours un nombre entier dont la valeur est inférieure à la taille du tableau. Lorsqu’on insére une paire de <key, value> dans une HashMap, elle doit donc générer un entier unique qui correspondra à la position de la value dans son tableau interne. Pour cela, elle utilise la méthode hashCode() sur l’objet key. Ainsi, lorsqu’on voudra récupérer un élément de la HashMap avec get(Object key), la HashMap va une nouvelle fois utilise hashCode() sur l’objet key pour générer l’entier correspondant à la position de la value dans son tableau interne.

Comme vous vous en doutez, ce mécanisme de mapping est beaucoup plus efficace que de comparer une par une toutes les key de la map pour récupérer la value associée. Et pour que ce mécanisme reste efficace, il faut à tout prix éviter les collisions, qui interviennent lorsque certains objets d’une map possèdent le même hashcode.

Pour terminer cet article, précisons qu’il faut être très vigilant lorsque l’on crée des POJO mutables. Par opposition à immutable, un objet mutable est un objet dont la valeur des attributs peut être modifiée. Ainsi, si un POJO contient des setters, il est forcément mutable. Dans notre exemple, on pourrait alors se retrouver dans une situation comme celle-ci :

List<Person> list = new ArrayList<Person>();
Person p1 = new Person("toto", 10);
Person p2 = new Person("toto", 10);
list.add(p1);
System.out.println(list.contains(p2)); //imprime true

p1.setName("tata");
System.out.println(list.contains(p2)); //imprime false

Lorsque l’on utilise la méthode contains(), et donc equals(), sur des objets mutables, il faut faire attention à ne pas modifier l’état des objets une fois qu’ils ont été insérés dans une structure de données.

  1. Pas encore de commentaire
  1. Pas encore de trackbacks