《Effective Java》筆記

2. 建立和銷燬對象

1. 靜態工廠方法替代構造器

優勢:html

  1. 名稱清晰
  2. 每次調用沒必要new對象
  3. 能夠返回原返回類型任意子類型對象
  4. 返回的對象能夠隨着調用而發生改變
  5. 返回的對象所屬的類,在編寫該靜態工廠方法的類時能夠不存在
    缺點:
  6. private 構造器致使,就不能有子類,子類構造器會默認訪問父類構造器

2. 多個構造器參數時可使用構建器(建造者模式 Builder)

印象比較深入的是:以前寫安卓用到了OkHttp,使用的OkHttp是用Kotlin寫的,其中實例化對象用的就是這個建造者模式,當時覺得是Kotlin鏈式調用的某種語法特性,後來才知道是設計模式java

主要用於多參數時,避免重疊構造器和避免無參構造器建立對象依次set參數過程當中JavaBean可能處於的不一致狀態git

3. 私有化構造器或者枚舉類型強化Singleton屬性

Singleton常見實現方法:github

  1. final修飾的公有靜態成員
  2. 靜態工廠
  3. 單元素Enum

經過放射調用私有構造器,能夠修改構造器,建立第二個實例時拋出異常
序列化時除了實現 Serializable接口,還須要提供readResolve,防止反序列化建立新的實例設計模式

4. 私有構造器強化不可實例化

5. 優先考慮依賴注入來引用資源

SOLID 原則中的 D 依賴反轉原則 (Dependency Inversion Principle),依賴注入是該原則的一種實現方式
建立一個新的實例時,就將該資源傳到構造器中數組

6. 避免建立沒必要要的對象

  1. 不使用 new String() 方式建立String實例,使用 String s ="hello"; 方式,同一臺虛擬機的代碼,字符串字面常量相同,該對象就會被重用
  2. 複用建立成本較高的實例:正則Pattern實例
  3. 優先使用基本類型,自動裝箱必定程度下降性能
    注意!在提倡保護性拷貝時,重用對象付出代價遠大於建立重複對象

7. 清除過時的對象引用

  1. 棧pop時應將pop的對象設置爲null
  2. 避免緩存內存泄漏的一種方式:WeakHashMap,除了WeakHashMap的鍵以外,若是沒有存在對某個鍵的引用,會被自動刪除
  3. 監聽器和其餘回調形成的內存泄漏:只保留他們的弱引用,例如保存成WeakHashMap的鍵

8.避免使用終結方法和清除方法

終結方法 (finalizer) 和 清除方法(cleaner JDK9) 都不可預測且會形成性能損失
注重時間的任務不該該使用這兩種方法來完成
不該該依賴這兩種方法來更新重要的持久狀態(好比:釋放共享資源,可能還沒開始釋放資源,系統就垮掉了)
TODO p25 終結方法攻擊(finalizer attack)
TODO 合理用途:緩存

  1. 安全網,忘記close
  2. 回收對象的本地對等體(native peer)

9. try-with-resources 優先於try-finalyy

實現了AutoCloseable 接口安全

  1. 優雅
  2. 避免底層物理設備異常致使第一個異常被第二個異常抹除,增長排錯成本

3. 對於全部對象都通用的方法

10. 覆蓋Equals時請注意遵照通用約定

不用覆蓋的狀況(知足其一便可)架構

  1. 類的每一個實例本質都惟一
  2. 類無需提供邏輯相等功能
  3. 父類的equals方法足夠知足使用
  4. 類是私有或者默認權限或肯定不會調用到equals

覆蓋equals的通用規範ide

  1. 自反性 非null值,x.equals(x) 爲true 子類和父類不一樣的equals方法,相互equals會違反自反性
  2. 對稱性 非null值,x.equals(y) 等於 y.equals(x)
  3. 傳遞性 非null值,x.equals(y) 爲ture 且 y.equals(z) 爲true -> x.equals(z)爲true 子類相較於父類增長了一些用於equals的屬性,可是子類還使用父類equals,違反傳遞性
  4. 一致性 非null值,值未修改,屢次調用equals 結果應一致
  5. x非null值,x.equals(null) 返回 false

子類與父類 自反性和傳遞性的對立:沒法再拓張可實例化的類的同時,既增長新的值組件,同時又保留equals約定

IDEA 默認子類equals寫法就是:使用getClass() 比較對象,而後調用父類equals最後對比子類拓展的屬性

Stream 初始化Set:

private static final Set<Point> unitCircle = Stream.of(
            new Point(1, 0),
            new Point(0, 1),
            new Point(-1, 0),
            new Point(0, -1)
    ).collect(Collectors.toCollection(HashSet::new));

辨析:instanceof getClass()==

  • instanceof 這個對象是否爲這個類或其子類的實例
  • getClass()== 運行時期對象的類

使用複合優於繼承:提供私有Point域以及共有視圖(view)方法

JDK反例:public class Timestamp extends java.util.Date,在同一個集合中使用或者其餘方式混合使用,可能有不正確的行爲

instanceof 第一個操做符爲null 那麼返回的必定爲false,使用instanceof能夠省略null判斷

一致性,不要使equals方法依賴於不可靠的資源,JDK反例:URL equals

高質量equals訣竅

  1. == 檢查是否爲這個對象的引用
  2. instanceof 檢查是否爲正確類型(同時也能夠排除掉null)
  3. 轉換爲正確的類型
  4. 檢查每一個關鍵域
    • 基本類型: ==
    • 浮點數:Float.compare(float,float) Double.compare(double,double) 使用Float.equals或Double.equals 自動裝箱減低性能
    • 數組域:Arrays.equals
    • 合法null:Objects.equals(Object,Object) 避免拋出空指針異常
    • 順序上按照:最有可能不一致或開銷最小的域

注意點:

  1. 覆蓋equals時總要覆蓋hashCode
  2. 不要過分尋找等價關係,好比考慮別名形式
  3. 不要把equals參數定義爲非Object 這樣是重載而非重寫

11. 覆蓋equals時總要覆蓋hashCode

  1. 同個對象屢次調用hashCode返回同一個值
  2. equals(Object) 相等 hashCode返回整數也相等
  3. equals(Object) 不相等 hashCode 有可能相等

Object的hashCode方法爲native方法:public native int hashCode();
hashCode註釋提到:hashCode返回的是由對象存儲地址轉化獲得的值

As much as is reasonably practical, the hashCode method defined by
    class {@code Object} does return distinct integers for distinct
    objects. (This is typically implemented by converting the internal
    address of the object into an integer, but this implementation
    technique is not required by the
    Java&trade; programming language.)

若是沒有覆蓋hashCode致使兩個相同實例具備不一樣散列碼,HashMap有一項優化,能夠將每一個項相關聯的散列碼緩存起來,若是散列碼不匹配,不會校驗對象相等性
好的散列函數傾向於「爲不相等的對象產生不相等的散列碼」,每一個對象都被映射到同一個散列桶中,會實其退化爲鏈表

簡單解決方法:

  1. 定義 int result ,初試化爲對象第一個關鍵域散列碼
  2. 對每一個關鍵域f完成這些步驟,獲得散列碼c
    • 計算f散列值:基本類型 包裝類.hashCode(f);對象引用遞歸調用hashCode,或者爲域計算一個範式,範式調用hashCode;null返回0;數組中沒有重要元素用常數代替,都很重要用Arrays.hashCode(f)
    • 累加:result = 31* result + c;
  3. 返回 result

使用31緣由:

  1. 31爲奇素數,避免乘以偶數致使的乘法移除信息丟失
  2. 乘以31能夠用移位和減法代替 31*i == (i<<5) -1
計算機在進行數值運算的時候,是經過補碼錶示每一個數值的
正數原反補相同;負數反碼符號位不變,其它位都取反;負數的補碼在反碼的基礎上加1

Java 三種位運算(補碼)
<< 左移:丟棄最高位,0補最低位
>> 右移:符號位不變,左邊填充符號位
>>> 無符號右移:忽略了符號位,左邊填充0

Objects類:public static int hash(Object... values) 便捷,可是相對速度慢一些:可變參數引起數組建立,基本類型須要拆箱裝箱
不可變類用使用private 變量 緩存hash值, 延遲初始化(lazily initialize)

構造器爲:PhoneNumber(short areaCode, short prefix, short lineNum) ,必須強轉 (short)1
直接傳入整數,否者報錯,沒有int類型構造器

注意:

  1. 不要經過排除關鍵域來提升性能,反而可能致使實例被映射在極少數散列碼上
  2. 不要對hashCode返回值作具體規定,可能影響其在將來的改進

12. 始終要覆蓋toString

Object實現:類名稱@散列碼無符號十六進制表示
toString 返回對象中包含的全部值得關注的信息

能夠在文檔中指定返回的格式,並配套靜態工廠或者構造器,便於相互轉換,JDK例子:BigInteger、BigDecimal、包裝類
靜態工具類和大多數枚舉類編寫toString意義不大

13. 謹慎地覆蓋clone

記得實現Cloneable接口(空的interface),否者拋出異常:java.lang.CloneNotSupportedException
Object中的clone方法:protected native Object clone() throws CloneNotSupportedException;

TODO p46
實現Cloneable接口的類是爲了提供一個功能適當複雜的公有clone方法,它無需調用構造器就能夠建立對象
不變類永遠都不該該提供clone方法

Clone方法就是另外一個構造器;必須保證它不會傷害到原始對象,並確保正確地建立被克隆對象中的約束條件

  1. 遞歸調用clone拷貝內部信息
  2. 若是域含有對象數組,要注意遞歸或迭代深拷貝

若是域是final修飾,clone是禁止給final域賦值,Cloneable架構於引用可變對象的final域的正經常使用法是不相兼容的

線程安全:Object類 clone 沒有同步

實現了Cloneable接口的類

  1. 都應該覆蓋clone方法,且是共有方法,返回類型爲自己
  2. 調用super.clone()
  3. 修正域(深拷貝)

拷貝對象更好的方法是提供拷貝構造器和拷貝工廠

最佳實踐:用clone複製數組

14. 考慮實現Comparable接口

Comparable接口:public int compareTo(T o);
將這個對象與指定對象比較,大於、等於、小於指定對象返回負整數、零和正整數,類型不匹配拋出RuntimeException:ClassCastException

通用約定

  1. sgn(x.compareTo(y)) == -sgn(y.compareTo(x))
  2. 可傳遞:(x.compareTo(y) > 0) && (y.compareTo(z) > 0) -> (x.compareTo(z) > 0)
  3. (x.compareTo(y) == 0) 全部的z知足:sgn(x.compareTo(z) == 0) == sgn(y.compareTo(z))
    建議:(x.compareTo(y) == 0) -> x.equals(y)

依賴比較關係的類有:TreeSet TreeMap Collections Arrays

與equals相同:沒法在用新的值組件拓展課實例化的類時,同時保持compareTo約定,除非放棄面向對象抽象優點;能夠經過組合方式實現Comparable接口的類增長值組件(提供「視圖」 view方法)

BigDecimal d1 = new BigDecimal("1.0");
        BigDecimal d2 = new BigDecimal("1.00");
        System.out.println(d1.equals(d2)); // false
        System.out.println(d1.compareTo(d2)); // 0
        Set<BigDecimal> bigDecimals = new HashSet<>();
        // equals 比較
        bigDecimals.add(d1);
        bigDecimals.add(d2);
        System.out.println(bigDecimals); // [1.0, 1.00] 

        Set<BigDecimal> treeSets = new TreeSet<>();
        // compareTo 比較
        treeSets.add(d1);
        treeSets.add(d2);
        System.out.println(treeSets); // [1.0]

注意Double和Float 使用compare比較而非 < >
Java7 提供了包裝類的靜態compare方法,建議在compareTo中使用

從關鍵域開始逐步比較全部域,某個域產生非零結果當即返回

Java 8 提供了Comparator接口,簡潔,可是要付出性能成本

private static final Comparator<PhoneNumber> COMPARATOR =
            comparingInt((PhoneNumber pn) -> pn.areaCode)
                    .thenComparingInt((PhoneNumber pn) -> pn.prefix)
                    .thenComparingInt((PhoneNumber pn) -> pn.lineNum);

    @Override
    public int compareTo(PhoneNumber pn) {
        return COMPARATOR.compare(this, pn);
    }

參考資料

GitHub effective-java-3e-source-code
Effective Java - 豆瓣
java中instanceof和getClass()的做用
Initializing HashSet at the Time of Construction
Java Object.hashCode()源碼分析
通俗易懂的 Java 位操做運算講解
Java 位運算(移位、位與、或、異或、非)

相關文章
相關標籤/搜索