Effective Java讀書筆記八

         

Item9:當覆寫equals方法時,一定要覆寫hashCode方法

這一條非常重要,再次提一下:覆寫了equals方法就一定要覆寫hashCode方法 。因爲如果不這麼做的話就會違反hashCode的準則,從而讓基於hash的colloections——HashMap,HashSet等——的使用出現問題。

下面是hashCode方法的準則:

  • 對同一個對象調用多次hashCode方法,得到的是同一個整數,但是這個整數在同一個Application多次執行中不一定相同。
  • 如果equals方法判斷兩個對象相等,那麼這兩個對象調用hashCode得到的整數也相同。
  • 當equals判斷兩個對象不相等時,不用保證hashCode產生的整數也不相等;不過如果hashCode方法產生的整數也不同的話,可以提高hash表的性能

忘記覆寫hashCode方法的最關鍵問題就是上面的第二條。

當我們覆寫了equals方法後,就不能再使用Object的hashCode方法了,否則會讓我們放入hash表中的key用相等的對象卻無法取出。另外,也不要自作聰明,將hashCode直接覆寫成返回一個固定的整數,這麼做會讓hash表直接退化成一個鏈表,讓性能從O(n)直接躍到O(n2 )。

既然覆寫hashCode這麼重要,那麼這裏就記錄一下覆寫的一般步驟:

  1. 先創建一個整型result,任意賦一個非零整數,比如17;
  2. 對在equals方法中使用的每一個字段f,做如下操作:
    a. 計算每一個字段的hash code,記做c:
        i.   如果字段類型是boolean,那麼c = (f?1:0);
        ii.   如果字段爲byte,char,short或者int,那麼c = (int)f;
        iii.  如果字段類型爲long,那麼c = (int)(f ^ (f>>>32));
        iv.  如果字段類型爲float,那麼c = Float.floatToIntBits(f);
        v.   如果字段類型爲double,那麼先調用Double.doubleToLongBits(f),然後再用第2.a.ii步計算c;
        vi.  如果字段是另外一個對象的引用,那麼如果覆寫的equals方法中判斷此字段時使用的是該字段的equals方法,那麼我們只需要調用這個字段的hashCode方法即可;如果equals方法中的比較方式很複雜,那麼就需要建立一個正規的形式,用來計算hashCode方法;如果這個字段是null,一般情況下,返回0就可以(其它值也行);
        vii.  如果字段類型是數組的話,跟進equals方法中使用的元素來分別計算並相加它們的hashCode,如果數組的所有元素都用在了equals方法中,那麼我們可以直接調用Arrays.hashCode;
    b. 將在2.a中得到的整數c加到1的result中去:
        result = 31 * result + c
  3. 返回result;
  4. 完成後別忘了編寫測試,要保證hashCode符合上面提到的準則

注意:和覆寫equals方法一樣,根據其它字段計算而來的字段不必用來計算hashCode,我們可以直接忽略它們。

上面步驟中,步驟1選擇的17是任意的,步驟2.b的計算中選擇的31是有些原因的,因爲31是一個奇素數,如果是偶數的話,計算中一旦溢出了,那麼值就會丟失,因爲乘2相當於做右移操作。另外,31可以用位移和減法來代替,從而提高性能:31 * i == (i << 5) - i。

如果類是immutable的,並且hashCode的計算非常的複雜或者影響性能,那麼我們可以考慮再類的內部增加一個字段用來保存計算好的hashCode,這樣當第一次調用hashCode方法時,將計算好的結果存到這個字段中,以後每次調用hashCode方法都直接從該字段中取值,可以提高程序的性能。

最後需要強調的是,千萬不要爲了提高性能而在計算hashCode時省略了某些字段 。這麼做可能會在計算hashCode時提高速度,但是別忘了,所有的hash表結構都會因此降低性能,最後的結果是得不償失的。

 

Item10:儘量覆寫toString方法

JavaSE6規範中提到,toString方法是用來提供簡明、易讀的信息的。也就是說,不覆寫toString方法並沒有什麼問題,但是toString方法天生就是用來幫助我們瞭解對象的,覆寫它可以讓我們更容易的使用對象。

當我們把對象作爲參數傳遞給println和printf方法,或者用來和字符串做加法連接的時候,程序會自動調用對象的toString方法。

在我們覆寫toString方法時,應該包含所有對象中有用的信息。

不過需要注意的一點是關於toString的Java Doc的:在覆寫toString方法時我們可以有兩種選擇,一種是提供一個固定的格式,並將這個格式記錄到Java Doc中;另一種則不提供固定格式。如果決定提供一個固定的格式,那麼就需要在Java Doc中記述清楚,同時最好可以提供一些方法讓使用這個對象的人將固定格式的字符串轉換爲合法的對象。如果不提供固定的格式,那麼就不要在Java Doc中記述和格式有關的東西,防止使用它的人依賴於這個格式後,新版本升級後無法向下兼容。但是,不管是否提供固定格式,都需要在Java Doc中解釋清楚覆寫toString方法的意圖。

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