EffectiveJava 1創建和銷燬對象 9覆蓋equals時總要覆蓋hashCode

1    重點關注

1.1    本章核心

覆蓋equals時總要覆蓋hashCode,代碼參考3.1

 

1.2    hashcode的散列函數針對不同數據類型的算法(3.1參考short類型)  

一個好的散列函數通常傾向於“爲不相等的對象產生不相等的散列碼”,理想情況下,散列函數應該把集合中不相等的實例均勻地分佈到所有可能的散列值上,下面給出一種簡單的解決辦法:

1.把某個非零的常數值,比如說17,保存在一個名爲result的int類型的變量中
2.對於對象中每個關鍵域f(指equals方法中涉及的每個域),完成以下步驟:

1)爲該域計算int類型的散列碼c:

a. 如果該域是boolean類型,則計算(f ? 1 : 0)
b. 如果該域是byte、char、short或者int類型,則計算(int)f
c. 如果該域是long類型,則計算(int)(f ^ (f>>>32))
d. 如果該域是float類型,則計算Float.floatToIntBits(f)
e. 如果該域是double類型,則計算Double.doubleToLongBits(f),然後按照步驟c,爲得到的long類型值計算散列值
f. 如果該域是一個對象引用,並且該類的equals方法通過遞歸地調用equals的方式來比較這個域,則同樣爲這個域遞歸地調用hashCode,如果需要更復雜的比較,則爲這個域計算一個“範式”,然後針對這個範式調用hashCode,如果這個域的值爲null,則返回0
g. 如果該域是一個數組,則要把每一個元素當做單獨的域來處理,也就是說,遞歸地應用上述規則,對每個重要的元素計算一個散列碼,然後根據2)中的做法把這些散列值組合起來。如果數組域中的每個元素都很重要,可以利用Arrays.hashCode方法
2)按照下面的公式,把步驟1)中計算得到的散列碼c合併到result中:

result = 31 * result + c;
// 31有個很好的特性,即用移位和減法來代替乘法,可以得到更好的性能:31 * i == (i<<5) - i
1
2
3)返回result

4)寫完了hashCode方法之後,要編寫單元測試來驗證

 

1.3    覆蓋hashCode方法通用約定:

1、在應用程序的執行期間,只要對象的equals方法的比較操作所用到的信息沒有被修改,那麼對同一對象的多次調用,hashCode方法都必須始終返回同一個值。在一個應用程序與另外一個應用程執行過程中,執行hsahCode方法所返回的值可以不一致。
2、如果兩個對象根據equals(Object)方法比較是相等的,那麼調用這兩個對象中的hashCode方法都必須產生同樣的整數結果。
3、如果兩個對象根據equals(Object)方法比較是不相等的,那麼調用這兩個對象中的hashCode方法,則不一定要求hashCode方法必須產生不同的結果。但開發者應該知道,給不相等的對象產生截然不同的整數結果,有可能提高散列表的性能。

 

1.4    避免重寫hashcode方法影響程序性能

可以參考懶加載的方法

https://www.cnblogs.com/1446358788-qq/p/11365927.html

 

 

2    課程內容

2.1    課程內容主要   

參見1

 

2.2    課外:short類型使用

參見3.1  main方法標紅部分

 

2.3    乘法溢出問題解決

https://www.cnblogs.com/jiading/articles/13200625.html

 

2.4    移位運算

參考百度百科

https://baike.baidu.com/item/%E7%A7%BB%E4%BD%8D%E8%BF%90%E7%AE%97%E7%AC%A6/5622348?fr=aladdin

 

3    代碼演練

3.1    本章核心

package com.ddwei.test.core.chapter9;

public class Demo1 {
    //懶加載
    private volatile int hashCode;

    @Override
    public int hashCode(){
        int result = hashCode;
        if(result == 0){
            result = 17;
            result = 17 * result+areaCode;
            result = 17 * result+prefix;
            result = 17 * result+lineNumber;
        }
        return result;
    }
}

 

3.2    推演反例

demo

package com.ddwei.test.core.chapter9.demo1;

import java.util.HashMap;
import java.util.Map;

public final class PhoneNumber {
    private final short areaCode;
    private final short prefix;
    private final short lineNumber;

    public PhoneNumber(short areaCode, short prefix, short lineNumber) {
        rangeCheck(areaCode, 999, "area code");
        rangeCheck(prefix, 999, "prefix");
        rangeCheck(lineNumber, 9999, "line number");
        this.areaCode = (short)areaCode;
        this.prefix = (short)prefix;
        this.lineNumber = (short)lineNumber;
    }
    private static void rangeCheck(int arg,int max,String name) {
        if(arg < 0 || arg > max)
            throw new IllegalArgumentException(name +": "+ arg);
    }

    @Override
    public boolean equals(Object obj) {
        //1、使用==操作符檢查“參數是否爲這個對象的引用”
        if(obj == this)
            return true;
        //2、使用instanceof操作符檢查“參數是否爲正確的類型”
        if(!(obj instanceof PhoneNumber))
            return false;
        //3、把參數轉化成正確的類型
        PhoneNumber pn = (PhoneNumber)obj;
        //4、對於該類的每個“關鍵”域,檢查參數中的域是否與該對象中對應的域相匹配(其實就是比較兩個對象的值是否相等了)
        return pn.lineNumber == lineNumber
            && pn.prefix == prefix
            && pn.areaCode == areaCode;
    }

    public static void main(String[] args) {
        Map<PhoneNumber,String> m = new HashMap<PhoneNumber,String>();
        short s1 = 707;
        short s2 = 867;
        short s3 = 5309;
        m.put(new PhoneNumber(s1,s2,s3),"Jenny");
        System.out.println(m.get(new PhoneNumber(s1,s2,s3)));
    }
}

 

打印日誌

null

Process finished with exit code 0

 

 

3.3    推演正例

demo

package com.ddwei.test.core.chapter9.demo2;

import java.util.HashMap;
import java.util.Map;

public final class PhoneNumber {
    private final short areaCode;
    private final short prefix;
    private final short lineNumber;

    public PhoneNumber(short areaCode, short prefix, short lineNumber) {
        rangeCheck(areaCode, 999, "area code");
        rangeCheck(prefix, 999, "prefix");
        rangeCheck(lineNumber, 9999, "line number");
        this.areaCode = (short)areaCode;
        this.prefix = (short)prefix;
        this.lineNumber = (short)lineNumber;
    }
    private static void rangeCheck(int arg,int max,String name) {
        if(arg < 0 || arg > max)
            throw new IllegalArgumentException(name +": "+ arg);
    }

    @Override
    public boolean equals(Object obj) {
        //1、使用==操作符檢查“參數是否爲這個對象的引用”
        if(obj == this)
            return true;
        //2、使用instanceof操作符檢查“參數是否爲正確的類型”
        if(!(obj instanceof PhoneNumber))
            return false;
        //3、把參數轉化成正確的類型
        PhoneNumber pn = (PhoneNumber)obj;
        //4、對於該類的每個“關鍵”域,檢查參數中的域是否與該對象中對應的域相匹配(其實就是比較兩個對象的值是否相等了)
        return pn.lineNumber == lineNumber
            && pn.prefix == prefix
            && pn.areaCode == areaCode;
    }

    public static void main(String[] args) {
        Map<PhoneNumber,String> m = new HashMap<PhoneNumber,String>();
        short s1 = 707;
        short s2 = 867;
        short s3 = 5309;
        m.put(new PhoneNumber(s1,s2,s3),"Jenny");
        System.out.println(m.get(new PhoneNumber(s1,s2,s3)));
    }

    //懶加載
    private volatile int hashCode;

    @Override
    public int hashCode(){
        int result = hashCode;
        if(result == 0){
            result = 17;
            result = 17 * result+areaCode;
            result = 17 * result+prefix;
            result = 17 * result+lineNumber;
        }
        return result;
    }

}

 

 

打印日誌

Jenny

Process finished with exit code 0

 

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