又在javaEye發現了一篇好文章,迫切轉過來呵呵
http://www.javaeye.com/topic/269601
什麼時候需要重寫equals()?
我們知道每一個java類都繼承自Object類,equals()是Object類中提供的方法之一。那麼,讓我們先來看看Object#equals()在Java中的原代碼:
public boolean equals(Object obj)
{
return (this == obj);
}
可以看出,只有當一個實例等於它本身的時候,equals()纔會返回true值。通俗地說,此時比較的是兩個引用是否指向內存中的同一個對象,也可以稱做是否實例相等。而我們在使用equals()來比較兩個指向值對象的引用的時候,往往希望知道它們邏輯上是否相等,而不是它們是否指向同一個對象。在這樣的情況下, 如果超類也沒有重寫equals()以實現期望的行爲,這時我們就需要重寫equals方法。而且這樣做也使得這個類的實例可以被用做映射表(map)的鍵,或者集合(set)的元素,並使映射表或者集合表現出預期的行爲。
Object類提供的equals方法只是一個很簡單的,不能適應應用程序有特殊要求的情況。
比如網絡對象,帶有volatile屬性的對象,或是帶有多層子對象的複合對象,等等,是不能像String一類的對象進行簡單比較的,所以提供了這樣一個機制,就像serializable接口一樣,既有默認的序列化方法,也提供了程序自己定製,覆蓋默認方式的可能性。
Object僅僅提供了一個對引用的比較,如果兩個引用不是同一個那就返回false,這是無法滿足大多數對象比較的需要的,所以要覆蓋。
怎樣重寫equals()方法?
重寫equals()方法看起來非常簡單,但是有許多改寫的方式會導致錯誤,並且後果非常嚴重。要想正確改寫equals()方法,你必須要遵守它的通用約定。下面是約定的內容,來自java.lang.Object的規範:
equals方法實現了等價關係(equivalence relation):
1. 自反性:對於任意的引用值x,x.equals(x)一定爲true。
2. 對稱性:對於任意的引用值x 和 y,當x.equals(y)返回true時,
y.equals(x)也一定返回true。
3. 傳遞性:對於任意的引用值x、y和z,如果x.equals(y)返回true,
並且y.equals(z)也返回true,那麼x.equals(z)也一定返回true。
4. 一致性:對於任意的引用值x 和 y,如果用於equals比較的對象信息沒有被修
改,多次調用x.equals(y)要麼一致地返回true,要麼一致地返回false。
5. 非空性:對於任意的非空引用值x,x.equals(null)一定返回false。
接下來我們通過實例來理解上面的約定。我們首先以一個簡單的非可變的二維點類作爲開始:
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;
}
//override equasl()
public boolean equals(Object o){
if(!(o instanceof ColorPoint))
return false;
ColorPoint cp = (ColorPoint)o;
return super.equals(o) && cp.color==color;
}
}
我們重寫了equals方法,只有當實參是另一個有色點,並且具有同樣的位置和顏色的時候,它才返回true。可這個方法的問題在於,你在比較一個普通點和一個有色點,以及反過來的情形的時候,可能會得到不同的結果:
public static void main(String[] args){
Point p = new Point(1, 2);
ColorPoint cp = new ColorPoint(1, 2, Color.RED);
System.out.println(p.equals(cp));
System.out.println(cp.eqauls(p));
}
運行結果:
true
false
這樣的結果顯然違反了對稱性,你可以做這樣的嘗試來修正這個問題:讓ColorPoint.equals在進行“混合比較”的時候忽略顏色信息:
public boolean equals(Object o){
if(!(o instanceof Point))
return false;
//如果o是一個普通點,就忽略顏色信息
if(!(o instanceof ColorPoint))
return o.equals(this);
//如果o是一個有色點,就做完整的比較
ColorPoint cp = (ColorPoint)o;
return super.equals(o) && cp.color==color;
}
這種方法的結果會怎樣呢?讓我們先來測試一下:
public static void main(String[] args){
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));
System.out.println(p2.equals(p1));
System.out.println(p2.equals(p3));
System.out.println(p1.eqauls(p3));
}
運行結果:
true
true
true
false
這種方法確實提供了對稱性,但是卻犧牲了傳遞性(按照約定,p1.equals(p2)和p2.eqauals(p3)都返回true,p1.equals(p3)也應返回true)。要怎麼解決呢?事實上,這是面嚮對象語言中關於等價關係的一個基本問題。要想在擴展一個可實例化的類的同時,既要增加新的特徵,同時還要保留equals約定,沒有一個簡單的辦法可以做到這一點。新的解決辦法就是不再讓ColorPoint擴展Point,而是在ColorPoint中加入一個私有的Point域,以及一個公有的視圖(view)方法:
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;
}
//返回一個與該有色點在同一位置上的普通Point對象
public Point asPoint(){
return point;
}
public boolean equals(Object o){
if(o == this)
return true;
if(!(o instanceof ColorPoint))
return false;
ColorPoint cp = (ColorPoint)o;
return cp.point.equals(point)&&
cp.color.equals(color);
}
}
還有另外一個解決的辦法就是把Point設計成一個抽象的類(abstract class),這樣你就可以在該抽象類的子類中增加新的特徵,而不會違反equals約定。因爲抽象類無法創建類的實例,那麼前面所述的種種問題都不會發生。
重寫equals方法的要點:
1. 使用==操作符檢查“實參是否爲指向對象的一個引用”。
2. 使用instanceof操作符檢查“實參是否爲正確的類型”。
3. 把實參轉換到正確的類型。
4. 對於該類中每一個“關鍵”域,檢查實參中的域與當前對象中對應的域值是否匹
配。對於既不是float也不是double類型的基本類型的域,可以使用==操作符
進行比較;對於對象引用類型的域,可以遞歸地調用所引用的對象的equals方法;
對於float類型的域,先使用Float.floatToIntBits轉換成int類型的值,
然後使用==操作符比較int類型的值;對於double類型的域,先使用
Double.doubleToLongBits轉換成long類型的值,然後使用==操作符比較
long類型的值。
5. 當你編寫完成了equals方法之後,應該問自己三個問題:它是否是對稱的、傳
遞的、一致的?(其他兩個特性通常會自行滿足)如果答案是否定的,那麼請找到
這些特性未能滿足的原因,再修改equals方法的代碼。