| 導購 | 订阅 | 在线投稿
分享
 
 
 

深入equals方法

來源:互聯網網民  2007-02-02 20:08:57  評論

深入equals方法

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;才是正

確的比較。

另外一個quick check是if(this==obj) return true;

是否equals方法一定比較的兩個對象就一定是要同一類型?上面我用了"通常",這也是絕大多數程序

員的願望,但是有些特殊的情況,我們可以進行不同類型的比較,這並不違反規範。但這種特殊情況

是非常罕見的,一個不恰當的例子是,Integer類的equals可以和Sort做比較,比較它們的value是不

是同一數學值。(事實上JDK的API中並沒有這樣做,所以我才說是不恰當的例子)

在完成quick check以後,我們就要真正實現你認爲的“相等”。對于如果實現對象相等,沒有太高

的要求,比如你自己實現的“人”類,你可以認爲只要name相同即認爲它們是相等的,其它的sex,

ago都可以不考慮。這是不完全實現,但是如果是完全實現,即要求所有的屬性都是相同的,那麽如

何實現equals方法?

class Human{

private String name;

private int ago;

private String sex;

....................

public boolean equals(Object obj){

quick check.......

Human other = (Human)ojb;

return this.name.equals(other.name)

&& this.ago == ohter.ago

&& this.sex.equals(other.sex);

}

}

這是一個完全實現,但是,有時equals實現是在父類中實現,而要求被子類繼承後equals能正確的工

作,這時你並不事實知道子類到底擴展了哪些屬性,所以用上面的方法無法使equals得到完全實現。

一個好的方法是利用反射來對equals進行完全實現:

public boolean equals(Object obj){

quick check.......

Class c = this.getClass();

Filed[] fds = c.getDeclaredFields();

for(Filed f:fds){

if(!f.get(this).equals(f.get(obj)))

return false;

}

return true;

}

爲了說明的方便,上明的實現省略了異常,這樣的實現放在父類中,可以保證你的子類的equals可以按

你的願望正確地工作。

關于equals方法的最後一點是:如果你要是自己重寫(正確說應該是履蓋)了equals方法,那同時就一

定要重寫hashCode().爲是規範,否則.............

我們還是看一下這個例子:

public final class PhoneNumber {

private final int areaCode;

private final int exchange;

private final int extension;

public PhoneNumber(int areaCode, int exchange, int extension) {

rangeCheck(areaCode, 999, "area code");

rangeCheck(exchange, 99999999, "exchange");

rangeCheck(extension, 9999, "extension");

this.areaCode = areaCode;

this.exchange = exchange;

this.extension = extension;

}

private static void rangeCheck(int arg, int max, String name) {

if(arg < 0 || arg > max)

throw new IllegalArgumentException(name + ": " + arg);

}

public boolean equals(Object o) {

if(o == this)

return true;

if(!(o instanceof PhoneNumber))

return false;

PhoneNumber pn = (PhoneNumber)o;

return pn.extension == extension && pn.exchange == exchange && pn.areaCode == areaCode;

}

}

注意這個類是final的,所以這個equals實現沒有什麽問題。

我們來測試一下:

public static void main(String[] args) {

Map hm = new HashMap();

PhoneNumber pn = new PhoneNumber(123, 38942, 230);

hm.put(pn, "I love you");

PhoneNumber pn1 = new PhoneNumber(123, 38942, 230);

System.out.println(pn);

System.out.println("pn.equals(pn1) is " + pn.equals(pn1));

System.out.println(hm.get(pn1));

System.out.println(hm.get(pn));

}

既然pn.equals(pn1),那麽我put(pn,"I love you");後,get(pn1)這什麽是null呢?

答案是因爲它們的hashCode不一樣,而hashMap就是以hashCode爲主鍵的。

所以規範要求,如果兩個對象進行equals比較時如果返回true,那麽它們的hashcode要求返回相等的值。

好了,休息,休息一下。。。。。。。。。。。。。。。。

 
特别声明:以上内容(如有图片或视频亦包括在内)为网络用户发布,本站仅提供信息存储服务。
 
深入equals方法 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;才是正 確的比較。 另外一個quick check是if(this==obj) return true; 是否equals方法一定比較的兩個對象就一定是要同一類型?上面我用了"通常",這也是絕大多數程序 員的願望,但是有些特殊的情況,我們可以進行不同類型的比較,這並不違反規範。但這種特殊情況 是非常罕見的,一個不恰當的例子是,Integer類的equals可以和Sort做比較,比較它們的value是不 是同一數學值。(事實上JDK的API中並沒有這樣做,所以我才說是不恰當的例子) 在完成quick check以後,我們就要真正實現你認爲的“相等”。對于如果實現對象相等,沒有太高 的要求,比如你自己實現的“人”類,你可以認爲只要name相同即認爲它們是相等的,其它的sex, ago都可以不考慮。這是不完全實現,但是如果是完全實現,即要求所有的屬性都是相同的,那麽如 何實現equals方法? class Human{ private String name; private int ago; private String sex; .................... public boolean equals(Object obj){ quick check....... Human other = (Human)ojb; return this.name.equals(other.name) && this.ago == ohter.ago && this.sex.equals(other.sex); } } 這是一個完全實現,但是,有時equals實現是在父類中實現,而要求被子類繼承後equals能正確的工 作,這時你並不事實知道子類到底擴展了哪些屬性,所以用上面的方法無法使equals得到完全實現。 一個好的方法是利用反射來對equals進行完全實現: public boolean equals(Object obj){ quick check....... Class c = this.getClass(); Filed[] fds = c.getDeclaredFields(); for(Filed f:fds){ if(!f.get(this).equals(f.get(obj))) return false; } return true; } 爲了說明的方便,上明的實現省略了異常,這樣的實現放在父類中,可以保證你的子類的equals可以按 你的願望正確地工作。 關于equals方法的最後一點是:如果你要是自己重寫(正確說應該是履蓋)了equals方法,那同時就一 定要重寫hashCode().爲是規範,否則............. 我們還是看一下這個例子: public final class PhoneNumber { private final int areaCode; private final int exchange; private final int extension; public PhoneNumber(int areaCode, int exchange, int extension) { rangeCheck(areaCode, 999, "area code"); rangeCheck(exchange, 99999999, "exchange"); rangeCheck(extension, 9999, "extension"); this.areaCode = areaCode; this.exchange = exchange; this.extension = extension; } private static void rangeCheck(int arg, int max, String name) { if(arg < 0 || arg > max) throw new IllegalArgumentException(name + ": " + arg); } public boolean equals(Object o) { if(o == this) return true; if(!(o instanceof PhoneNumber)) return false; PhoneNumber pn = (PhoneNumber)o; return pn.extension == extension && pn.exchange == exchange && pn.areaCode == areaCode; } } 注意這個類是final的,所以這個equals實現沒有什麽問題。 我們來測試一下: public static void main(String[] args) { Map hm = new HashMap(); PhoneNumber pn = new PhoneNumber(123, 38942, 230); hm.put(pn, "I love you"); PhoneNumber pn1 = new PhoneNumber(123, 38942, 230); System.out.println(pn); System.out.println("pn.equals(pn1) is " + pn.equals(pn1)); System.out.println(hm.get(pn1)); System.out.println(hm.get(pn)); } 既然pn.equals(pn1),那麽我put(pn,"I love you");後,get(pn1)這什麽是null呢? 答案是因爲它們的hashCode不一樣,而hashMap就是以hashCode爲主鍵的。 所以規範要求,如果兩個對象進行equals比較時如果返回true,那麽它們的hashcode要求返回相等的值。 好了,休息,休息一下。。。。。。。。。。。。。。。。
󰈣󰈤
王朝萬家燈火計劃
期待原創作者加盟
 
 
 
>>返回首頁<<
 
 
 
 
 
 熱帖排行
 
 
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有