Effective Java——Item8:改寫equals的時候總是要改寫hashCode
引自 http://www.cnblogs.com/wxf0701/archive/2008/04/24/1169809.html
更多Effective Java 內容,參見 http://www.cnblogs.com/wxf0701/tag/java+/
/**
*在改寫equals的時候總是要改寫hashCode,如果不這樣的話,就會違反Object.hashCode的通用約定,
*導致這個類無法與所有基於雜湊值的集合類結合在一起正常工作,包括HashMap,HashSet和Hashtable
*hashCode的約定內容:
*hashCode()返回該物件的雜湊碼值。支援該方法是為雜湊表提供一些優點.
*(1)在Java應用程式執行期間,在同一物件上多次呼叫
*前提是物件上equals比較中所用的資訊沒有被修改。從某一應用程式的一次執行到同一應用程式的
*另一次執行,該整數無需保持一致。
*(2)如果根據equals(Object)方法,兩個物件是相等的,那麼在兩個物件中的每個物件上呼叫
*hashCode方法都必須生成相同的整數結果。(不改寫hashCode違反的關鍵是本條)
*(3)以下情況不是必需的:如果根據equals(java.lang.Object)方法,兩個物件不相等,那麼在兩個物件
*中的任一物件上呼叫hashCode方法必定會生成不同的整數結果。但是,程式設計師應該知道,為不相等的物件生
*成不同整數結果可以提高雜湊表的效能。*
*/
publicclass PhoneNumber {
privatefinalshortareaCode;
privatefinalshortexchange;
privatefinalshortextension;
public PhoneNumber(int areaCode, int exchange,
int extension) {
rangeCheck(areaCode,999, "area code");
rangeCheck(exchange,999, "exchange");
rangeCheck(extension, 9999,
this.areaCode= (short) areaCode;
this.exchange= (short) exchange;
this.extension = (short) extension;
}
privatestaticvoid rangeCheck(int arg, int max,
String name) {
if (arg < 0 || arg > max)
thrownew IllegalArgumentException(name +": " + arg);
}
publicboolean equals(Object o) {
if (o == this)
returntrue;
if (!(o instanceof PhoneNumber))
returnfalse;
PhoneNumber pn = (PhoneNumber)o;
return pn.extension == extension &&
pn.exchange== exchange&&
pn.areaCode== areaCode;
}
/**如果沒有改寫hasCode方法,當將該類與HashMap結合使用時:
*Mapm=newHashMap();
*m.put(newPhoneNumber(408,867,"Jenny");
*當你期望m.get(newPhoneNumber(408,867,"Jenny"))會返回"Jenny"時,它卻返回null.
*因為這裡涉及到兩個PhoneNumber例項,前者插入到HashMap中,後者與前者相等,用於檢索。
*由於沒有改寫hashCode,導致兩個相等的例項具有不同的雜湊碼,違反了hashCode約定。
*/
/**Q:如何編寫hashCode方法?
*A:理想的雜湊函式應滿足第3條約定——為不相等的物件產生不相等的雜湊碼,
*把集合中不相等的例項均勻地分佈到所有可能的雜湊值上。好的“處方”:
*(1)把某個非零常數值,比如17,儲存在一個變數裡,比如:intresult=17;
*(2)對於物件中的每個關鍵域f,完成以下步驟
*a.如果該值是boolean型別,則計算(f?0:1)
*b.如果是byte,char,short或者int,則計算(int)f
*c.如果是long,則計算(int)(f^(f>>>32))
*d.如果是float,則計算Float.floatToIntBits(f)
*e.如果是double,則計算Double.doubleToLongBits(f)得到一個long型別的值,然後(2).c
*對long型值進行計算
*f.如果是一個物件引用,則同樣遞迴呼叫物件的hashCode.
*如果要求一個更復雜的比較,則為該域計算一個“規範表示(canonicalrepresentation)”,然後
*針對這個正規化呼叫hashCode.
*如果該域為null,則返回0(或其它某個常數)
*g.如果是一個數組,則把每一個元素當作一個單獨的域來處理
*(3)按公式計算雜湊碼result=37*result+c
*(4)返回result
*(5)寫完hashCode方法後,檢查“是否相等的例項具有相等的雜湊碼”*
*/
/*
//改寫後的雜湊函式
public int hashCode() {
int result = 17;
result = 37*result + areaCode;
result = 37*result + exchange;
result = 37*result + extension;
return result;
}
*/
/**
*注意:(1)如果一個類是非可變的,並且計算雜湊碼的代價比較大,那麼應考慮把雜湊碼快取在物件內部,而不是每次
*請求時都重新計算;
*(2)如果你覺得這種型別的大多數物件都會被用做雜湊鍵(hashkeys),那麼你應該在建立例項時就計算雜湊碼。
*否則,採用“延緩初始化”雜湊碼。
*(3)不要試圖從雜湊碼計算機中排除一個物件的關鍵部分以提高效能。
*/
/*Lazily initialized, cached hashCode
private volatile int hashCode = 0;// (See Item 48)
public int hashCode() {
if (hashCode == 0) {
int result = 17;
result = 37*result + areaCode;
result = 37*result + exchange;
result = 37*result + extension;
hashCode = result;
}
return hashCode;
}
*/
// ... // Remainder omitted
publicstaticvoid main(String[] args) {
Map m = new HashMap();
m.put(new PhoneNumber(408, 867, 5309), "Jenny");
System.out.println(m.get(new PhoneNumber(408, 867, 5309)));
}
}