第六條 在改寫equals的時候請遵守通用規定
有一種“值類”可以不要求改寫equals方法,類型安全枚舉類型,因為類型安全枚舉類型保證每一個值之多只存在一個對象,所有對于這樣的類而言,Object的queals方法等同于邏輯意義上的equals方法。
在改寫equals方法的時候,要遵循的規范:
1,自反性。對任意的引用值x,x.equals(x)一定是true
2,對稱性。對于任意的引用值x和y,當且僅當y.equals(x)返回true時,x.equals(y)也一定返回true.
3,傳遞性。對于任意的引用值x,y和z,如果x.equals(y)==true and y.equals(z)==true,so x.equals(z)==true.
4,一致性。對于任意的引用值x和y,如果用于equals比較的對象信息沒有被修改的話,那么,多次調用x.equals(y)要么一致地返回true,要么一致地返回false.
5,非空性。對于任意的非空引用值x,x.equals(null)一定返回false.
自反性:要求一個對象必須等于其自身。一個例子:你把該類的實例加入到一個集合中,則該集合的contains方法
將果斷地告訴你,該集合不包含你剛剛加入的實例.
對稱性:
例如:
public final class CaseInsensitiveString{
private String s;
public CaseInsensitiveString(String s){
if(s==null) throw new NullPointerException();
this.s=s;
}
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 but s.equals(cis)==false
這就違反了對稱性的原則.為了解決這個問題,只需把企圖與String互操作的這段代碼從equals方法中去掉舊可以了.這樣做之后,你可以重構代碼,使他變成一條返回語句:
public boolean equals(Object o){
return o instanceof CaseInsensitiveString && ((CaseInsensitiveString)o).s.equalsIgnoreCase(s);
}
傳遞性---即如果一個對象等于第二個對象,并且第二個對象又等于第三個對象,則第一個對象一定等于第三個對象。
例如:
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;
}
}
現在我們來擴展這個類,為一個點增加顏色信息:
public class ColorPoint extends Point{
private Color color;
public ColorPoint(int x,int y,Color color){
super(x,y);
this.color=color;
}
}
分析: equals方法會怎么樣呢?如果你完全不提供equals方法,而是直接從Point繼承過來,那么在equals座比較的時候顏色信息會被忽略掉。如果你編寫一個equals方法,只有當實參是另一個有色點,并且具有同樣的位置和顏色的時候,它才返回true:
public boolean equals(Object o){
if(!(o instanceof ColorPoint)) return false;
ColorPoint cp=(ColorPoint)o;
return super.equals(o) && cp.color==color;
}
分析:這個方法的問題在于,我們在比較一個普通點和一個有色點,以及反過來的情形的時候,可能會得到不同的結果。前一種比較忽略了顏色信息,而后一種比較總是返回false,因為實參的類型不正確。例如:
Point p=new Point(1,2);
ColorPoint cp=new ColorPoint(1,2,Color.RED);
然后:p.equals(cp)==true but cp.equals(p)==false
修正:讓ColorPoint.equals在進行“混合比較”的時候忽略顏色信息:
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;
}
這種方法確實提供了對稱性,但是卻犧牲了傳遞性:
ColorPoint p1=new ColorPoint(1,2,Color.RED);
Point p2=new Point(1,2);
ColorPoint p3=new ColorPoint(1,2,Color.BLUE);
此時:p1.equals(p2)==true and p2.equals(p3)==true but p1.equals(p3)==false很顯然違反了傳遞性。前兩個比較不考慮顏色信息,而第三個比較考慮了顏色信息。
結論:要想在擴展一個可實例華的類的同時,既要增加新的特征,同時還要保留equals約定,沒有一個簡單的辦法可以做到這一點。根據"復合優于繼承",這個問題還是有好的解決辦法:我們不讓ColorPoint擴展Point,而是在ColorPoint中加入一個私有的Point域,以及一個公有的視圖方法,此方法返回一個與該有色 點在同一位置上的普通Point對象:
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;
}
public 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.equals(color);
}
}
注意,你可以在一個抽象類的子類中增加新的特征,而不會違反equals約定。
一致性:
如果兩個對象相等的話,那么它們必須始終保持相等,除非有一個對象被修改了。由于可變對象在不同的時候可以與不同的對象相等,而非可變對象不會這樣,這個約定沒有嚴格界定。
非空性:沒什么好說的。
1,使用==操作符檢查“實參是否為指向對象的一個應用”。如果是的話,則返回true。
2,使用instanceof操作符檢查“實參是否為正確的類型”。如果不是的話,則返回false。
3,把實參轉換到正確的類型。
4,對于該類中每一個“關鍵(significant)”域,檢查實參中的域與當前對象中對應的域值是否匹配
if (!(this.field == null ? o.field == null : this.equals(o.field)))
//或者寫成 if(!(this.field == o.field || (this.field != null && this.field.equals(o.field)))) 對于this.field和o.field通常是相同的對象引用,會更快一些。
return false;
//比較下一個field
//都比較完了
return true;
5.最后還要確認以下事情
5.1)改寫equals的同時,總是(必須)要改寫hashCode方法(見【第8條】),這是極容易被忽略的,有極為重要的
5.2)不要企圖讓equals方法過于聰明
5.3)不要使用不可靠的資源。如依賴網絡連接
5.4)不要將equals聲明中的Object對象替換為其他類型。
public boolean equals(MyClass) 這樣的聲明并不鮮見,往外使程序員數小時搞不清楚為什么不能正常工作
原因是這里是重載(overload)而并不是改寫(override)(或稱為覆蓋、重寫)
相當于給equals又增加了一個實參類型為MyClass的重載,而實參為Object的那個equals并沒有被改寫,依然還是從Object繼承來的最初的那個equals,所總是看不到程序員想要的效果。因為類庫或其他人寫的代碼都是調用實參為Object型的那個equals方法的(別人又如何在事前知曉你今天寫的MyClass呢?)