一、覆蓋equals請遵守通用約定
1、覆蓋equals的約定
自反性:對於任何非null的引用值x,x.equals(x)必須返回true。
對稱性:對於任何非null的引用值x和y,當且僅當y.equals(x)返回true時,x.equals(y)必須返回true。
傳遞性:對於任何非null的引用值x、y和z,如果x.equals(y)返回true,並且y.equals(z)返回true,那麼x.equals(z)也必須返回true。
一致性:對於任何非null的引用值x和y,只要equals的比較操作在對象中所用的信息沒有被修改,多次調用x.equals(y)就會一致地返回true,或者一致的返回false。
對於任何非null的引用值x,x.equals(null)必須返回false。
2、違反對稱性的案例:
public class CaseInsensitiveString {
private final String s;
public CaseInsensitiveString(String s){
if(s==null)
throw new NullPointerException();
this.s = s;
}
@Override
public boolean equals(Object o) {
if(o instanceof CaseInsensitiveString){
return s.equalsIgnoreCase(((CaseInsensitiveString) o).s);
}
if(o instanceof String)
return s.equalsIgnoreCase((String)o);
return false;
}
}
對於以上代碼:如果假設現在有兩個字符串,一個不區分大小寫的,另一個是普通字符串
CaseInsensitiveString cis = new CaseInsensitiveString("Polish");
String s = "polish";
cis.equals(s); //返回true
s.equals(cis); //返回false
雖然CaseInsensitiveString中的equals知道普通的字符串對象,但是String類中的equals方法並不知道不區分大小寫的字符串,因此s.equals(cis)返回false。這就違反了對稱性
3、違反對稱性或傳遞性的案例:
public class Point {
private final int x;
private final int y;
public Point(int x,int y){
this.x = x;
this.y = y;
}
@Override
public boolean equals(Object o) {
if(!(o instanceof Point)) return false;
Point p = (Point)o;
return p.x == x && p.y == y;
}
}
現在擴展當前類,爲點增加顏色屬性
public 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 o) {
if(!(o instanceof ColorPoint)) return false;
return super.equals(o) && ((ColorPoint)o).color == color;
}
}
這個問題在於,比較普通點和有色點,以及有色點和普通點反過來對比時可能會得到不同的結果。普通點和有色點比較時忽略了顏色信息,但有色點和普通點比較時永遠返回false因爲參數的類型不正確。這違反了對稱性,改進一下ColorPoint的equals方法讓它符合對稱性,但會犧牲傳遞性;
@Override
public boolean equals(Object o) {
if(!(o instanceof Point)) return false;
if(!(o instanceof ColorPoint)) return o.equals(this);
return super.equals(o) && ((ColorPoint)o).color == color;
}
這方法解決了對稱性問題但犧牲了傳遞性
ColorPoint p1 = new ColorPoint(1, 2,Color.RED);
Point p2 = new Point(1,2);
ColorPoint p3 = new ColorPoint(1, 2,Color.BLUE);
System.out.println(p1.equals(p2)); //返回true
System.out.println(p2.equals(p3)); //返回true
System.out.println(p1.equals(p3)); //返回false
因爲前兩個比較中忽略了顏色信息,第三個比較中比較了顏色,並且顏色不同,所以返回false;
注意:我們無法在擴展可實例化的類的同事,既增加新的值組件,同時又保留equals約定,除非願意放棄面向對象的抽象所帶來的的優勢。但是我們可以在一個抽象類的子類中增加新的值組件,而不會違反equals約定。
防止違反一致性,無論類是否可變,都不要使equals方法依賴於不可靠的資源。
4、實現高質量equals的方法的訣竅:
a、使用==操作符檢查“參數是否爲這個對象的引用”。
b、使用instanceof操作符檢查“參數是否爲正確的類型”。
c、把參數轉換爲正確的類型。
d、對於該類中的每個“關鍵(significant)”域,檢查參數中的域是否與該對象中對應的域相匹配。如果這些測試全部返回成功,則返回true,否則返回false。
注意:1、覆蓋equals方法時總要覆蓋hashcode方法;2、不要企圖讓equals方法過於智能;3、不要將equals聲明中的Object對象替換爲其他的類型。