Point
Cet exemple a pour but de montrer que les propriétés de
l'égalité interdisent certaines relations
d'héritage. Il est tiré de
Joshua Bloch, "Effective Java,
Programming Language Guide",
Addison-Wesely 2001.
equals(Object)
La méthode equals(Object) doit respecter les
règles de l'égalité, c'est à dire celles
d'une relation d'équivalence : soient o1, o2
et o3 des instances de Object,
- reflexivité :
o1.equals(o1) doit toujours
être égal à true ;
- symétrie :
o1.equals(o2) et o2.equals(o1)
ont toujours la même valeur ;
- transitivité : si
o1.equals(o2) et o2.equals(o3)
valent true, alors o1.equals(o3)
doit
également valoir true.
D'autre part, cette méthode doit être consistante, c'est à dire
que si o1 et o2 ne changent pas, alors o1.equals(o2)
ne doit pas changer non plus.
Les classes Point et ColorPoint
On se donne une classe Point :
public class Point {
private final int x;
private final int y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
public boolean equals(Object o) {
if (!(o instanceof Point))
return
false;
Point p = (Point) o;
return p.x == x &&
p.y ==
y;
}
// Remainder omitted
}
et une classe ColorPoint :
public class ColorPoint extends Point {
private Color color;
public ColorPoint(int x, int y, Color color) {
super(x, y);
this.color = color;
}
// Remainder omitted
}
La méthode equals(Object o) de ColorPoint
Comment définir une méthode equals(Object o)
pour ColorPoint ?
Voici une première proposition :
public boolean equals(Object o) {
if (!(o instanceof
ColorPoint))
return
false;
ColorPoint cp = (ColorPoint)
o;
return super.equals(o)
&&
cp.color == color;
}
Cette solution viole la règle de symétrie. Le
résultat du code suivant :
Point p = new Point(10,10);
ColorPoint cp = new ColorPoint(10, 10, Color.RED);
System.out.println(cp.equals(p));
System.out.println(p.equals(cp));
est :
false
true
Pour éviter ce problème, on propose le code
suivant :
public boolean
equals(Object o) {
if (!(o instanceof Point))
return
false;
if (!(o instanceof
ColorPoint))
return
o.equals(this);
ColorPoint cp = (ColorPoint)
o;
return super.equals(o)
&&
cp.color == color;
}
Cette solution viole la règle de transitivité. Le
résultat du code suivant :
Point p = new Point(10, 10);
ColorPoint cp1 = new
ColorPoint(10, 10, Color.BLUE);
ColorPoint cp2 = new
ColorPoint(10, 10, Color.RED);
System.out.println(cp1.equals(p));
System.out.println(p.equals(cp2));
System.out.println(cp1.equals(cp2));
est :
true
true
false
Quelle solution ?
En fait, il n'y a pas de solution, car on cherche une
égalité entre des objets de natures différentes !
L'erreur vient du fait qu'un point coloré n'est pas de
même nature qu'un point sans couleur. Par conséquent, ColorPoint
ne doit pas hériter de Point. On utilisera donc la
délégation.
public class ColorPoint {
private Point point;
private Color color;
public ColorPoint(int x, int y, Color color) {
point = new Point(x, y);
this.color = color;
}
// Returns the point-view of this color point.
Point asPoint() {
return point;
}
public boolean equals(Object o) {
if (!(o instanceof
ColorPoint))
return
false;
ColorPoint cp = (ColorPoint)
o;
return
cp.point.equals(point)
&& cp.color == color;
}
// Remainder omitted
}
Les vues
Pour pouvoir utiliser les méthodes prenant un Point
en paramètre, il est judicieux de fournir une vue sur le Point
contenu dans une instance de ColorPoint. Une vue est
un
objet qui "adapte" tout ou partie des données d'un objet
à un autre type. Par exemple, la méthode Map.keySet()
retourne une vue de type Set sur les clés
d'une table. Toute intervention sur l'ensemble des
clés retourné aura un effet sur la table et inversement.
Dans le cas de ColorPoint, la vue est retournée
par la méthode asPoint(). Le fait qu'une instance
de la classe Point ne puisse être modifiée
rend la solution très simple : il suffit de renvoyer la
référence sur le point contenu dans le point
coloré. Si les points sont modifiables, la solution sera plus
complexe. L'exemple ci-dessous montre une solution dans le cas d'un
point modifiable et où un point coloré doit signaler tout
déplacement à l'aide d'un méthode privée showMove().
public class Point {
private int x;
private int y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
public void moveTo(int x, int y) {
this.x = x;
this.y = y;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
public boolean equals(Object o) {
if (!(o instanceof Point))
return
false;
Point p = (Point) o;
return p.x == x &&
p.y ==
y;
}
// Remainder omitted
}
public class ColorPoint {
private Color color;
private Point point;
private class InternalPoint extends Point {
InternalPoint(int x, int y)
{
super(x,
y);
}
public void moveTo(int x,
int y) {
super.moveTo(x, y);
showMove();
}
}
public ColorPoint(int x, int y, Color color) {
point = new InternalPoint(x,
y);
this.color = color;
}
public void moveTo(int x, int y) {
point.moveTo(x,y);
}
public int getX() {
return point.getX();
}
public int getY() {
return point.getY();
}
private void showMove() {
System.out.println("moving
to ("
+ point.getX() + "," + point.getY() + ")");
}
// Returns the point-view of this color point.
Point asPoint() {
return point;
}
public boolean equals(Object o) {
if (!(o instanceof
ColorPoint))
return
false;
ColorPoint cp = (ColorPoint)
o;
return
cp.point.equals(point)
&& cp.color == color;
}
// Remainder omitted
}