平時很難遇到需要覆蓋equals的情況。
什麼時候不需要覆蓋equals?
如果要問什麼時候需要覆蓋equals?
答案正好和之前的問題相反。
即,類需要一個自己特有的邏輯相等概念,而且超類提供的equals不滿足自己的行為。
(PS:對於枚舉而言,邏輯相等和對象相等都是一回事。)
既然只好覆蓋equals,我們就需要遵守一些規定:
其實這些規定隨便拿出一個都是很好理解的。
難點在於,當我遵守一個規定時有可能違反另一個規定。
自反性就不用說了,很難想想會有人違反這一點。
關於對稱性,下面提供一個反面例子:
class CaseInsensitiveString {
private final String s;
public CaseInsensitiveString(String s) {
if (s == null)
this.s = StringUtils.EMPTY;
else
this.s = s;
}
@Override
public boolean equals(Object obj) {
if (obj instanceof CaseInsensitiveString)
return s.equalsIgnoreCase(((CaseInsensitiveString) obj).s);
if (obj instanceof String)
return s.equalsIgnoreCase((String) obj);
return false;
}
}
這個例子顯然違反對稱性,即x.equals(y)為true 但 y.equals(x)為false。
不僅是在顯示調用時,如果將這種類型作為泛型放到集合之類的地方,會發生難以預料的行為。
而對於上面這個例子,在equals方法中我就不牽扯其他類型,去掉String實例的判斷就可以了。
關於傳遞性,即,當x.equals(y)為true 且 y.equals(z)為true 則 x.equals(z)為true。
這個規定在對類進行擴展時尤其明顯。
比如,我用x,y描述某個Point:
class Point {
private final int x;
private final int y;
public Point(int x, int y) {
super();
this.x = x;
this.y = y;
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof Point))
return false;
Point p = (Point) obj;
return p.x == x && p.y == y;
}
}
現在我想給Point加點顏色:
class ColorPoint extends Point {
private final Color color;
public ColorPoint(int x, int y, Color color) {
super(x, y);
this.color = color;
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof ColorPoint))
return false;
return super.equals(obj) && ((ColorPoint) obj).color == color;
}
}
似乎很自然的提供了ColorPoint的equals方法,但他連對稱性的沒能滿足。
於是我們加以修改,令其滿足對稱性:
@Override
public boolean equals(Object obj) {
if (!(obj instanceof Point))
return false;
if (!(obj instanceof ColorPoint))
return obj.equals(this);
return super.equals(obj) && ((ColorPoint) obj).color == color;
}
好了,接下來我們就該考慮傳遞性了。
比如我們現在有三個實例,1個Point和2個ColorPoint....
然後很顯然,不滿足<當x.equals(y)為true 且 y.equals(z)為true 則 x.equals(z)為true>。
事實上,我們無法在擴展可實例化類的同時,既增加新的值組件,又保留equals約定。
於是我索性不用instanceof,改用getClass()。
這個確實可以解決問題,但很難令人接受。
如果我有一個子類沒有覆蓋equals,此時equals的結果永遠是false。
既然如此,我就放棄繼承,改用復合(composition)。
以上面的ColorPoint作為例子,將Point變成ColorPoint的field,而不是去擴展。 代碼如下:
public class ColorPoint {
private final Point point;
private final Color color;
public ColorPoint(int x, int y, Color color) {
if (color == null)
throw new NullPointerException();
point = new Point(x, y);
this.color = color;
}
/**
* Returns the point-view of this color point.
*/
public Point asPoint() {
return point;
}
@Override
public boolean equals(Object o) {
if (!(o instanceof ColorPoint))
return false;
ColorPoint cp = (ColorPoint) o;
return cp.point.equals(point) && cp.color.equals(color);
}
@Override
public int hashCode() {
return point.hashCode() * 33 + color.hashCode();
}
}
關於一致性,即如果兩者相等則始終相等,除非有一方被修改。
這一點與其說equals方法,到不如思考寫一個類的時候,這個類應該設計成可變還是不可變。
如果是不可變的,則需要保證一致性。
考慮到這些規定,以下是重寫equals時的一些建議: