Comparaison d'objets¶
La méthode equals()¶
La méthode equals() en Java détermine si l’objet qui appelle la méthode est égal à l’objet qui est passé en argument.
L'opérateur == vérifie si deux objets sont identiques : il compare que les deux objets possèdent la même référence mémoire et sont donc en fait le même objet.
Considérez le programme Java suivant :
class Point {
private double x, y;
public Point(double x, double y) {
this.x = x;
this.y = y;
}
}
public class Main {
public static void main(String[] args) {
Point p1 = new Point(5, 10);
Point p2 = new Point(5, 10);
if (p1 == p2) {
System.out.println("Égal");
} else {
System.out.println("Pas égal");
}
}
}
Pourquoi il a affiché Pas égal?
La raison est simple: lorsque nous comparons p1 et p2, il vérifie si p1 et p2 se réfèrent au même objet (les variables d’objet sont toujours des références en Java).
Deux mêmes objets sont égaux s'ils possèdent la même référence évidement mais, deux objets distincts peuvent aussi être égaux si l'invocation de la méthode equals() du premier avec le second en paramètre renvoie true.
p1 et p2 se réfèrent à deux objets différents, donc la valeur ( p1 == p2 ) est false.
Alors, comment vérifions-nous l’égalité des valeurs à l’intérieur des objets?
Toutes les classes en Java héritent de la classe Object, directement ou indirectement.
La classe Object a quelques méthodes de base comme clone(), toString(), equals(), hashCode etc.
Nous pouvons remplacer la méthode equals() dans notre classe pour vérifier si deux objets ont les mêmes données ou non.
class Point {
private double x, y;
public Point(double x, double y) {
this.x = x;
this.y = y;
}
// Surcharger equals() pour comparer deux objets
@Override
public boolean equals(Object o) {
if (o == this) {
return true;
}
if (!(o instanceof Point)) {
return false;
}
Point p = (Point) o;
return Double.compare(this.x, p.x) == 0 && Double.compare(this.y, p.y) == 0;
}
}
La classe Object possède deux méthodes qui sont relatives à l'identité des objets : equals() et hashCode().
La méthode equals() permet de tester l'égalité de deux objets d'un point de vue sémantique.
La méthode hashCode() permet de renvoyer la valeur de hachage de l'objet sur lequel elle est invoquée.
Les spécifications imposent une règle à respecter lors de la redéfinition de ces méthodes : si une classe redéfinit la méthode equals() alors elle doit aussi redéfinir la méthode hashCode() et inversement.
Le comportement de ces deux méthodes doit être symétrique : si les méthodes hashCode() et equals() sont redéfinies alors elles doivent utiliser, de préférence, toutes les deux les mêmes champs, car deux objets qui sont égaux en utilisant la méthode equals() doivent obligatoirement avoir tous les deux la même valeur de retour lors de l'invocation de leur méthode hashCode().
L'inverse n'est pas forcément vrai.
Le hashcode ne fournit pas un identifiant unique pour un objet : de toute façon le hashcode d'un objet est de type int, ce qui limiterait le nombre d'instances possibles d'une classe.
Deux objets pouvant avoir le même hashcode, il faut alors, utiliser la méthode equals() pour déterminer s'ils sont identiques.
La redéfinition des méthodes equals() et hashcode() doit respecter quelques contraintes qui sont précisées dans la documentation de la classe Object :
- Symétrie : pour deux références
aetb, sia.equals(b), alors il faut obligatoirement queb.equals(a) - Réflexivité : pour toute référence non
null,a.equals(a)doit toujours renvoyertrue - Transitivité : si
a.equals(b)etb.equals(c)alorsa.equals(c) - Consistance avec la méthode
hashCode(): si deux objets sont égaux en invoquant la méthodeequals(), alors leur méthodehashCode()doit renvoyer la même valeur pour les deux objets - Pour toute référence non
null,a.equals(null)doit toujours renvoyer false
Lors de la redéfinition de la méthode equals(), il faut bien faire attention à respecter la signature de la méthode qui attend en paramètre une instance de type Object sinon c'est une surcharge qui se compilera sans soucis mais, qui ne sera pas invoquée pour tester l'égalité.
Pour éviter ce problème, il faut utiliser l'annotation @Override sur la redéfinition de la méthode, assurant ainsi une erreur à la compilation si la méthode n'est pas une redéfinition d'une méthode héritée.