equals方法的重要性毋須多言,只要你想比較的兩個對象不願是同一對象,你就應該實現equals方法,讓對象用你認為相等的條件來進行比較.下面的內容只是API的規范,沒有什麼太高深的意義,但我之所以最先把它列在這兒,是因為這些規范在事實中並不是真正能保證得到實現.
1.對於任何引用類型, o.equals(o)==true 成立.
2.如果 o.equals(o1)==true 成立,那麼o1.equals(o)==true也一定要成立.
3.如果 o.equals(o1)==true 成立且o.equals(o2)==true 成立,那麼o1.equals(o2)==true 也成立.
4.如果第一次調用o.equals(o1)==true 成立再o和o1沒有改變的情況下以後的任何次調用都成立.
5.o.equals(null)==true 任何時間都不成立.
以上幾條規則並不是最完整的表述,詳細的請參見API文檔.對於Object類,它提供了一個最最嚴密的實現,那就是只有是同一對象是,equals方法才返回true,也就是人們常說的引用比較而不是值比較.這個實現嚴密得已經沒有什麼實際的意義,所以在具體子類(相對於Object來說)中,如果我們要進行對象的值比較,就必須實現自己的equals方法.
先來看一下以下這段程序:
public boolean equals(Object obj)
{
if (obj == null) return false;
if (!(obj instanceof FieldPosition))
return false;
FieldPosition other = (FieldPosition) obj;
if (attribute == null) {
if (other.attribute != null) {
return false;
}
}
else if (!attribute.equals(other.attribute)) {
return false;
}
return (beginIndex == other.beginIndex
&& endIndex == other.endIndex
&& field == other.field);
}
這是JDK中java.text.FieldPosition的標准實現,似乎沒有什麼可說的.我信相大多數或絕大多數程序員認為,這是正確的合法的equals實現.畢竟它是JDK的API實現啊.還是讓我們以事實來說話吧:
package debug;
import java.text.*;
public class Test {
public static void main(String[] args) {
FieldPosition fp = new FieldPosition(10);
FieldPosition fp1 = new MyTest(10);
System.out.println(fp.equals(fp1));
System.out.println(fp1.equals(fp));
}
}
class MyTest extends FieldPosition{
int x = 10;
public MyTest(int x){
super(x);
this.x = x;
}
public boolean equals(Object o){
if(o==null) return false;
if(!(o instanceof MyTest )) return false;
return ((MyTest)o).x == this.x;
}
}
運行一下看看會打印出什麼:
System.out.println(fp.equals(fp1));打印true
System.out.println(fp1.equals(fp));打印flase
兩個對象,出現了不對稱的equals算法.問題出在哪裡(腦筋急轉彎:當然出在JDK實現的BUG)?我相信有太多的程序員(除了那些根本不知道實現equals方法的程序員外)在實現equals方法時都用過instanceof運行符來進行短路優化的,實事求是地說很長一段時間我也這麼用過。太多的教程,文檔都給了我們這樣的誤導。而有些稍有了解的程序員可能知道這樣的優化可能有些不對但找不出問題的關鍵。另外一種極端是知道這個技術缺陷的骨灰級專家就提議不要這樣應用。
我們知道,"通常"要對兩個對象進行比較,那麼它們"應該"是同一類型。所以首先利用instanceof運行符進行短路優化,如果被比較的對象不和當前對象是同一類型則不用比較返回false,但事實上,"子類是父類的一個實例",所以如果 子類 o instanceof 父類,始終返回true,這時肯定不會發生短路優化,下面的比較有可能出現多種情況,一種是不能造型成子類而拋出異常,另一種是父類的private 成員沒有被子類繼承而不能進行比較,還有就是形成上面這種不對稱比較。可能會出現太多的情況。那麼,是不是就不能用 instanceof運行符來進行優化?答案是否定的,JDK中仍然有很多實現是正確的,如果一個class是final的,明知它不可能有子類,為什麼不用 instanceof來優化呢?為了維護SUN的開發小組的聲譽,我不說明哪個類中,但有一個小組成員在用這個方法優化時在後加上了這樣的注釋:
if (this == obj) // quick check
return true;
if (!(obj instanceof XXXXClass)) // (1) same object?
return false;
可能是有些疑問,但不知道如何做(不知道為什麼沒有打電話給我......)那麼對於非final類,如何進行類型的quick check呢?
if(obj.getClass() != XXXClass.class) return false;
用被比較對象的class對象和當前對象的class比較,看起來是沒有問題,但是,如果這個類的子類沒有重新實現equals方法,那麼子類在比較的時候,obj.getClass() 肯定不等於XXXCalss.class,也就是子類的equals將無效,所以if(obj.getClass() != this.getClass()) return false;才是正確的比較。