1. 程式人生 > >Effective Java——Item8:改寫equals的時候總是要改寫hashCode

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,HashSetHashtable

*hashCode的約定內容:

*hashCode()返回該物件的雜湊碼值。支援該方法是為雜湊表提供一些優點.

*(1)Java應用程式執行期間,在同一物件上多次呼叫

hashCode方法時,必須一致地返回相同的整數,

*前提是物件上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,

"extension");

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)));

}

}