effective java 讀書筆記---第三章對於所有對象都通用的方法

20170409
8.覆蓋 equals 方法需要遵守的約定
自反性:非空對象,自身與自身equals返回 true
對稱性:非空對象a.equals(b) 與 b.equals(a)結果一致
傳遞性:非空對象 a與 b 相等 b 與 c 相等則 a 與 c 必然相等
一致性:非空對象 a.equals(b)的返回結果與方法調用次數無關(對象中的任何信息沒有被修改)
非空性:任何與空對象比較時都應該返回 false
一般如下進行 equals 方法
使用==操作符判斷對象是否爲同一個對象(非必須,僅用來做優化,注意 null==null 會返回 true)
使用 instance of 判斷對象是否爲同一個對象(使用這個方法可以不做 null 校驗,null對象instance of 必然返回 false,注意這一點只對一般對象有效,有時做特殊的子類校驗時可能會比較同一超類下的不同子類,此時可以使用超類來做 instance of)
把參數轉換成正確的類型(之前已經做過 instance of)
比較類中的每個關鍵域,優先比較那些可能不同的,和比較開銷較小的域,對於對象引用的比較需要注意 null 校驗field==null?o.field==null?field.equals(o.field) 如果 field 與o.field 通常是相同的對象引用則使用field==o.field||(field!=null&&field.equals(o.field)) 以提高效率
覆蓋 equals 方法時還需要注意以下:
覆蓋 equals 時總是需要覆蓋 hashCode
不要企圖讓 equals 方法變得過於智能
不要講 equals 方法中的 Object 參數轉換成其他類型對象,此時 equals 只是重載而並沒有覆蓋原有的 equals 方法

9.覆蓋 equals 方法必然要覆蓋 hashCode 方法
如果不這樣做,會使得該類無法結合基於散列的集合一起正常工作,例如hashMap,HashSet 等等
對象中每個關鍵域hashCode方法返回值可以按如下值:
boolean 返回 f?1:0
byte char short int 返回 (int)f
long 計算f^(f>>>32)
float floatToIntBits(f)
double doubleToLongBits(f)然後計算 long 的 hashCode
引用類型則使用某種範式來計算 hashCode 如果爲 null 通常返回0
如果爲數組則需要將每個元素當做單獨的域來處理,Arrays.hashCode 來處理
再將上述所有散列結果 c按照如下公式合併到 resultresult = 31*result + c 返回 result 即爲 hashCode 值
對於覆蓋的 hashCode 方法必須保證 equals 返回 true 時返回相同的 hashCode 值,必須排除 equals 計算中沒有用到的域
如果一個類是不可變得,且計算 hashCode 的開銷很大,可以將 hashCode 的計算結果緩存在對象內部,可以在對象初始化時就計算 hashCode,也可以使用延遲加載,在第一次調用 hashCode 時計算
不要試圖從散列碼計算中排除對象的關鍵部分來提高性能,這樣的話散列函數就會把實例映射到極少數的散列碼上,造成基於散列碼的集合將會顯示出平方級的性能指標

10.始終覆蓋 toString 方法

11.謹慎的覆蓋 clone 方法
Cloneable接口的目的是爲了表明對象可以被克隆,但它並沒與提供克隆方法,Object 的 clone 方法是受保護的.Cloneable 接口的作用決定了 clone 方法的實現行爲,如果實現了 Cloneable 接口 clone 方法就會返回該對象的逐域拷貝,否則就會拋出 CloneNotSupportedException,這是接口的一種非典型用法,不值得效仿
克隆方法會返回類的一個新的實例,同時也會要求拷貝類的內部數據,這一過程沒有調用構造器
克隆方法就是另一個構造器,必須確保不傷害到原有對象,並正確的創建克隆對象中的約束條件
clone 與引用可變對象的final 域的正常使用是不互相兼容的(不能調用對象中 final 域的克隆方法)
與構造方法一樣clone 不應該調用新對象中任何非final 的方法
覆蓋 clone 方法也需要定義成 producted 並拋出CloneNotSupportedException,並且該類不應該實現 Cloneable 接口,可以使子類選擇是否實現 Cloneable 接口
clone方法沒有同步,因此可能需要編寫同步的 clone 方法
所有實現了 Cloneable 接口的類都應該有一個共有方法覆蓋 clone.該方法首先調用 super.clone,然後修正任何需要修正的域(拷貝任何包含內部深沉結構的可變對象,並用指向新對象的引用代替原來指向這些對象的引用),雖然這些內部拷貝操作往往可以使用遞歸調用 clone 來完成,但這並不是最佳方法.如果該類只包含基本類型的域,或者指向不可變對象的引用,那麼多變情況是沒有域需要修正(也有例外,例如代表序列號,id或者代表對象創建時間的域)
因此需要謹慎覆蓋 clone 方法,如果擴展了一個實現了 cloneable 接口的類,那麼除了實現一行爲良好的 clone 方法之外,沒有選擇.否則最好提供某些其他途徑來代替對象拷貝,或者乾脆不提供這樣的功能
另一個實現對象拷貝的好方法是提供一個拷貝構造器或拷貝工廠.拷貝構造器是一個構造器,它唯一的參數是包含該構造器的類public Test(Test test) 拷貝工廠類似於拷貝構造器的靜態工廠public static Test newInstance(Test test) 這兩種方法都比 clone 方法更具優勢
因此對於一個爲了繼承而設計的類,如果沒有提供行爲良好的受保護的 clone 方法,它的子類就不可能實現 Colneable 接口

12.考慮實現Comparable 接口
實現了 Comparable 接口就表明它的實例具有內在的排序關係
compareTo 方法的通用約定與 equals 方法類似
將類對象與指定對象比較 小於 等於 大於時分別返回 負數 0 正數
compareTo方法與 equals 類似有自反性,傳遞性與對稱性
compareTo強烈建議返回值爲0時 equals 方法返回 true,但不是必須的,就是說在通常情況下 compareTo 需要與 equals 方法返回相同的結果,但如果一個類的 compareTo 與 equals 返回了不同的結果,它仍然能夠正常工作,但如果一個有序集合包含了該類,這個集合可能無法遵循相應集合接口的通用約定,這是因爲這些集合接口的通用約定是按照 equals 方法來定義的,但有序集合使用了 compareTo方法所施加的同等性測試,(例如,BigDecimal這兩個方法就返回不一樣的結果,一個BigDecimal(“1.0”)與 BigDecimal(“1.00”),他們的 equals 返回不相等,但 compareTo 返回相等,如果使用HashSet包含這兩元素,集合中就會出現兩個元素,但如果使用 TreeSet,集合中只會包含一個元素)
compareTo 方法通常先比較最重要的域,然後比較次重要的域,只要返回非零結果就立即返回代碼如下:

    @Override
    public int compareTo(TestBigDecimal o)
    {
        if (x < o.getX())
            return -1;
        if (x > o.getX())
            return 1;
        if (y < o.getY())
            return y;
        if (y > o.getY())
            return y;
        return 0;

考慮到該方法只要求指定返回值得符號,該方法可以做如下優化:

    @Override
    public int compareTo(TestBigDecimal o)
    {
        int result = x - o.getX();
        if (result != 0)
            return result;
        return y - o.getY();
    }

這個技巧能夠在這裏工作的很好,但需要注意如下問題:使用這個方法必須確認最大與最小閾值的可能差必須小於或者等於Integer.MAX_VALU,否則就不能使用這個方法,因爲一個有符號的32位整數還沒有大到足以表達任意兩個32位整數的差(如果i 是很大正整數,j 是很大負整數,那麼 i-j 很可能將溢出並返回一個負值)

發佈了52 篇原創文章 · 獲贊 32 · 訪問量 16萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章