以前只是知道hashcode是用來計算散列值的,用來存儲在hashmap中使用。今天在看ibatis.net的源碼,突然想起來上次遇到的問題。
我建立了一些wrapper類型用來包裹.net的基本類型,來解決數據庫中查找出來空類型的問題。
當我把一個wrapper類型的對象放到hashtable中時。雖然改寫了Equals方法,但是仍然無法利用另外一個wrapper類型的但是wrapper的value一樣的對象去索引它。
今天一下想起來可能是沒有重寫GetHashCode的方法,google一搜索,果然如此。轉載一下竹筍炒肉的一篇筆記如下。雖然是java的,但是同樣適用.net。
讀《Effective java 中文版》(9)
第8條:改寫equals時總是要改寫hashCode
java.lnag.Object中對hashCode的約定:
- 在一個應用程序執行期間,如果一個對象的equals方法做比較所用到的信息沒有被修改的話,則對該對象調用hashCode方法多次,它必須始終如一地返回同一個整數。
- 如果兩個對象根據equals(Object o)方法是相等的,則調用這兩個對象中任一對象的hashCode方法必須產生相同的整數結果。
- 如果兩個對象根據equals(Object o)方法是不相等的,則調用這兩個對象中任一個對象的hashCode方法,不要求產生不同的整數結果。但如果能不同,則可能提高散列表的性能。
看個不改寫hashCode導致使用hashMap不能出現預期結果的例子:
public final class PhoneNumber{
private final short areaCode;
private final short exchange;
private final short extension;
public PhoneNumber(int areaCode,int exchage,int extension){
rangeCheck(areaCode,999,"area code");
rangeCheck(exchange,999,"exchange");
rangeCheck(extension,9999,"extension");
this.areaCode=(short) areaCode;
this.exchange=(short) exchange;
this.extension=(short)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) reutrn true;
if (!(o instanceof PhoneNumber)) return false;
PhoneNumber pn=(PhoneNumber)o;
return pn.extension==extension && pn.exchange=exchange && pn.areaCode=areaCode;
}
//No hashCode method
...
}
現在有以下幾行程序:
Map m=new HashMap();
m.put(new PhoneNumber(1,2,3),"Jenny");
則m.get(new PhoneNumber(1,2,3))的返回值什麼?
雖然這個實例據equals是相等的,但由於沒改寫hashCode而致兩個實例的散列碼並不同(即違反第二條要求),因則返回的結果是null而不是"Jenny".
理想情況下,一個散列函數應該把一個集合中不相等的實例均勻地分佈到所有可能的散列值上,下面是接近理想的“處方”:
- 把某個非零常數值(如17)保存在一個叫result的int類型的變量中;
- 對於對象中每個關鍵字域f(指equals方法中考慮的每一個域),完成以下步驟:
- 爲該域計算int類型的散列碼c:
- 如果該域是bloolean類型,則計算(f?0:1)
- 如果該域是byte,char,short或int類型,則計算(int)f
- 如果該域是long類型,則計算(int)(f^(>>>32))
- 如果該域是float類型,則計算Float.floatToIntBits(f)
- 如果該域是double類型,則計算Double.doubleToLongBits(f)得一long類型值,然後按前述計算此long類型的散列值
- 如果該域是一個對象引用,則利用此對象的hashCode,如果域的值爲null,則返回0
- 如果該域是一個數組,則對每一個數組元素當作單獨的域來處理,然後安下一步的方案來進行合成
- 如果該域是bloolean類型,則計算(f?0:1)
- 利用下面的公式將散列碼c 組合到result中。result=37*result+c;
- 爲該域計算int類型的散列碼c:
- 檢查“相等的實例是否具有相等的散列碼?”,如果爲否,則修正錯誤。
依照這個處方,得PhoneNumber的hashCode方法:
public int hashCode(){
int result=17;
result=37*result+areaCode;
result=37*result+exchange;
result=37*result+extension;
return result;
}
如果計算散列碼的代價比較高,可以考慮用內部保存這個碼,在創建是生成或遲緩初始化生成它。不要試圖從散列碼計算中排除掉一個對象的關鍵部分以提高性能。