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
}