La réponse de Kilian Foth est excellente. J'aimerais juste ajouter l'exemple canonique * de la raison pour laquelle c'est un problème. Imaginez une classe de points entière:
class Point2D {
public int x;
public int y;
// constructor
public Point2D(int theX, int theY) { x = theX; y = theY; }
public int hashCode() { return x + y; }
public boolean equals(Object o) {
if (this == o) { return true; }
if ( !(o instanceof Point2D) ) { return false; }
Point2D that = (Point2D) o;
return (x == that.x) &&
(y == that.y);
}
}
Maintenant, passons à une classe 3D.
class Point3D extends Point2D {
public int z;
// constructor
public Point3D(int theX, int theY, int theZ) {
super(x, y); z = theZ;
}
public int hashCode() { return super.hashCode() + z; }
public boolean equals(Object o) {
if (this == o) { return true; }
if ( !(o instanceof Point3D) ) { return false; }
Point3D that = (Point3D) o;
return super.equals(that) &&
(z == that.z);
}
}
Super simple! Utilisons nos points:
Point2D p2a = new Point2D(3, 5);
Point2D p2b = new Point2D(3, 5);
Point2D p2c = new Point2D(3, 7);
p2a.equals(p2b); // true
p2b.equals(p2a); // true
p2a.equals(p2c); // false
Point3D p3a = new Point3D(3, 5, 7);
Point3D p3b = new Point3D(3, 5, 7);
Point3D p3c = new Point3D(3, 7, 11);
p3a.equals(p3b); // true
p3b.equals(p3a); // true
p3a.equals(p3c); // false
Vous vous demandez probablement pourquoi je publie un exemple aussi simple. Voici le piège:
p2a.equals(p3a); // true
p3a.equals(p2a); // FALSE!
Lorsque nous comparons le point 2D au point 3D équivalent, nous obtenons la valeur true, mais lorsque nous inversons la comparaison, nous obtenons la valeur false (car p2a échoue instanceof Point3D
).
Conclusion
Il est généralement possible d'implémenter une méthode dans une sous-classe de telle sorte qu'elle ne soit plus compatible avec la façon dont la super-classe s'attend à ce qu'elle fonctionne.
Il est généralement impossible d'implémenter equals () sur une sous-classe très différente d'une manière compatible avec sa classe parente.
Lorsque vous écrivez un cours que vous avez l'intention de permettre aux gens de sous-classer, c'est une très bonne idée d'écrire un contrat. précisant le comportement de chaque méthode. Mieux encore, un ensemble de tests unitaires que les personnes pourraient exécuter contre leur implémentation de méthodes annulées pour prouver qu’elles ne violaient pas le contrat. Presque personne ne le fait parce que c'est trop de travail. Mais si vous vous en souciez, c'est la chose à faire.
Comparateur est un excellent exemple de contrat bien défini . Ignorez simplement ce qui est dit .equals()
pour les raisons décrites ci-dessus. Voici un exemple de ce que le comparateur ne .equals()
peut pas faire .
Remarques
L'élément 8 de "Effective Java" de Josh Bloch était la source de cet exemple, mais Bloch utilise un ColorPoint qui ajoute une couleur au lieu d'un troisième axe et utilise des doubles au lieu d'ints. L'exemple Java de Bloch est essentiellement dupliqué par Odersky / Spoon / Venners qui a rendu leur exemple disponible en ligne.
Plusieurs personnes se sont opposées à cet exemple car si vous informez la classe parent de la sous-classe, vous pouvez résoudre ce problème. Cela est vrai s’il ya un nombre de sous-classes assez petit et si le parent les connaît toutes. Mais la question initiale concernait la création d'une API pour laquelle quelqu'un d'autre écrirait des sous-classes. Dans ce cas, vous ne pouvez généralement pas mettre à jour l'implémentation parent pour qu'elle soit compatible avec les sous-classes.
Prime
Comparator est également intéressant car il résout correctement le problème de la mise en œuvre de equals () Mieux encore, un modèle permet de résoudre ce type de problème d'héritage: le modèle de conception Stratégie. Les classes qui intéressent les gens de Haskell et de Scala constituent également le modèle de la stratégie. L'héritage n'est ni mauvais ni faux, c'est simplement délicat. Pour en savoir plus, consultez l'article de Philip Wadler Comment rendre le polymorphisme ad hoc moins ad hoc