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 方法必定會生成不同的整數結果。但是,程序員應該知道,爲不相等的對象生

 *    成不同整數結果可以提高哈希表的性能。 *

 */

public class PhoneNumber {

    private final short areaCode;

    private final short exchange;

    private final short extension;

 

    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;

    }

 

    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;

    }

 

   /**如果沒有改寫hasCode方法,當將該類與HashMap結合使用時:

    * Map m = new HashMap();

    * m.put(new PhoneNumber(408,867,"Jenny");

    * 當你期望m.get(new PhoneNumber(408,867,"Jenny"))會返回"Jenny"時,它卻返回null.

    * 因爲這裏涉及到兩個PhoneNumber實例,前者插入到HashMap中,後者與前者相等,用於檢索。

    * 由於沒有改寫hashCode,導致兩個相等的實例具有不同的散列碼,違反了hashCode約定。

    */

   

    /**Q:如何編寫hashCode方法?

     * A:理想的散列函數應滿足第3條約定——爲不相等的對象產生不相等的散列碼,

     *    把集合中不相等的實例均勻地分佈到所有可能的散列值上。好的處方

     *    (1)把某個非零常數值,比如17,保存在一個變量裏,比如:int result=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.

     *         如果要求一個更復雜的比較,則爲該域計算一個規範表示(canonical representation)”,然後

     *         針對這個範式調用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)如果你覺得這種類型的大多數對象都會被用做散列鍵(hash keys),那麼你應該在創建實例時就計算散列碼。

 *      否則,採用延緩初始化散列碼。

 *      (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

 

    public static void 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)));

    }

}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章