天天看點

effective java摘抄(二)

一、覆寫equals時總要覆寫hashCode

       1、equals()傳回true則hashcode必須相同;

       2、equals()傳回false但hashcode未必傳回不相同;

      對于hashcode

       1、相等的對象必須有相同的散列碼,合法性;

       2、不相等的對象盡可能有不同的散列碼,高效性;

二、實作合法高效的hashCode的簡單解決方法:

       1、把某個非零的常數值,比如17,儲存在一個名為result的int類型的變量中。

       2、對于對象中每個關鍵域f(指equals方法中涉及的每個域),完成以下步驟:

             a、為該域計算int類型的散列碼c:

                   i、如果該域是boolean類型,則計算(f?1:0)。

                   ii、如果該域是byte、char、short或者int類型,則計算(int)f。

                   iii、如果該域是long類型,則計算(int)(f^(f>>>32))。

                   iv、如果該域是float類型,則計算Float.floatToIntBits(f)。

                   v、如果該域是double類型,則計算Double.doubleToLongBits(f),然後按照步驟2.a.iii,為得到的long類型值計算散列碼。

                   vi、如果該域是一個對象引用,并且該類的equals方法通過遞歸的調用equals的方法來比較這個域,則同樣為這個域遞歸的調用hashCode。如果需要更複雜的比較,則為這個域計算一個“範式”,然後針對這個範式調用hashCode。如果這個域的值為null,則傳回0(或者其他幾個常數,但通常是0)。

                   vii、如果該域是一個數組,則要把每一個元素當做單獨的域來處理。也就是說,地櫃應用上述規則,對每個重要的元素計算一個散列碼,然後根據步驟2.b中的做法把這些散列值組合起來。如果數組域中的每個元素都很重要,可以利用Arrays.hashCode()方法。

              b、按照小面的公式,把步驟2.a中計算得到的散列碼c合并到result中:result = 31 * result + c;

       3、傳回result。

       4、寫完了hashCode方法之後需要編寫單元測試來驗證自測。

     注意:如果一個域的值可以根據參與計算的其他域值計算出來,則可以把這樣的域排除在外。必須排除equals比較計算中沒有用到的任何域,否則很可能違反hashCode預定的第二條。

       上述步驟1中用到一個非零的初始值,是以步驟2.a中計算的散列值為0的那些初始域,會影響到散列值。如果步驟1中的初始值為0,則整個散列值将不受這些初始域的影響,因為這些域會增加沖突的可能性,值17是任選的。-------------------智商不夠,看不懂這段話,看幾遍了還是看不懂,嗚嗚~~

        步驟2.b中的乘法部分是的散列值依賴于域的順序,如果一個類包含多個相似的域,這樣的乘法運算就會産生一個更好的散列函數。之是以選擇31,是因為它是奇素數。如果乘數是偶數,并且乘法溢出的話,資訊就會丢失,因為與2相乘等價于移位運算。使用素數的好處并不明顯,但習慣上都使用素數計算散列結果。31有個很好的特性,即用移位和減法來代替乘法,可以得到更好的性能:31 * i == (i << 5) -i。現代的VM可以自動完成這種優化。

繼續閱讀