Effective Java要點筆記

  • 類可以通過靜態工廠方法來提供客戶端,而不是通過構造器
  • 優點:

    • 自定義工廠名稱,提高可讀性
    • 可以工廠裡搞單例
    • 控制實例類是哪種子類

    總之是更加靈活,可讀性更高

    缺點:

    • 有可能會導致類無法子類化,因為一般搞工廠,就把構造器私有或受保護了
    • 有心的使用者會困惑,總是想看看到底是咋實例化的,單例? 多例? 創建時是否有init一些前置過程?
    • getInstance 約定俗成返回單例
    • newInstance 約定俗成多例
    • getType 一般把工廠方法寫在其它類(如專門的工廠類) 可根據Type入參來從工廠拿對應單例
    • newType 一般把工廠方法寫在其它類(如專門的工廠類) 可根據Type入參來從工廠拿對應多例
    • 多個構造參數,如果靈活多變,要考慮用構造器

    工作中如果構造函數有多個 且 特定 的話,我一般傾向寫兩三個函數簽名不一樣構造器。但是如果在構造參數很多且多變,要寫一個內部構建器,用builder模式,而不是大量重疊構造器。

    優點:

    • builder 可以一次構建實例對象,而JavaBean的方式雖然比構造器可讀性好點,但會使對象狀態處於不一致的狀態,線程安全維護成本太高了。因為總是要setter方法賦值
    • builder方式創建的實例是不可變的,無狀態的。
    • builder方式在進行構造時可以加入校驗參數的邏輯確保正確的通過builder構建實例
    • builder 可以在真正創建對象之前進行各種參數修改調整,甚至可以自動設置某些域
    • builder 因為是變化的,從抽離變化的角度來看,可以將builder設計成接口
    <code>public interface Builder { public T build(); }
    /<code>

    缺點:

    • 靜態內部類builder明顯代碼量增加了
    • 創建實例還得搞個builder 額外的性能開銷

    總結: 個人感覺構造參數穩定的情況下,即未來不會參數變化頻繁 && 參數比較少,還是使用重疊構造器的方式,感覺這也在好多源碼中約定俗成的。如果構造類時需要多個參數,特別是當大多數參數都是可選的時候,Builder 不失為一個很好的選擇。可讀性和安全性都能保障。

    • 再講單例實現常見的有三種 枚舉 靜態屬性或靜態塊 雙重檢查鎖
    • 不需要實例話的類儘量把構造器私有化,比如一些工具類,避免不必要的對象意外創建
    • 對象如果可重用,就少創建點但是如果因為多創建了實例而提高了程序的清晰性,間接性和功能性,也是一個好事兒
    • 消除某些過期的對象引用, 因為可能導致內存洩漏一種情形是 數組, 還有 緩存, 可以用WeakHashMap解決,但是必須保證所有的緩存項的生命週期是由該鍵的外部引用而不是由值決定時,WeakHashMap才有意義, LinkedHashMap 可以自定義緩存策略,LRU常用實現。監聽器和回調的內存洩漏風險。
    • 關於java的 finalize方法其實工作中極少用到它,一般都是顯示的public關閉資源的方法,讓客戶端去顯示關閉,服務端也可以配合try catch finally 寫個確保釋放資源的操作(萬一客戶端腦殘不調close方法),子類覆蓋finalize方法注意super調用父類的finalize方法。可以搞個private final 內部類 裡面有個回收外部類實例資源的方法,外部類私有屬性保持對內部類實例的一個引用。內部 外部類現在同生共死了,當外部類死掉的時候,內部類實例也要死,死的時候把外部類資源回收了。
    • 關於覆蓋equals的相關事項
    • 類的每個實例都只與他自身相等
    • 類是私有的或者是包級私有的,那麼可以確定它的equals方法永遠不會被調用,這時候需要覆蓋equals方法,防止被意外調用
    • 如果要判斷“邏輯相等“,且父類equals做不到這個功能的時候需要覆寫equals
    • 枚舉值類,因為是“每個值至多隻存在一個對象“ 的類, 搜衣不需要覆寫
    • equals含義的通用約定一定要遵守!!!沒有哪個類是孤立的。
    • equels方法訣竅:
    • == 判斷是否是同一個對象的引用
    • instanceof 進行類型檢查
    • 把參數轉換為正確的類型
    • 檢查參數的每個域是否一一對應的equals
    • 覆蓋equals必須覆蓋hashCode,相等的對象必須具有相同的hashCode值~
    • 不要將equals聲明中的Object對象替換為其它的類型,應該覆蓋Object的 equals方法
    • 要始終覆蓋toString方法,打印的信息更加具有可讀性
    • 要區別設計良好的模塊與設計不好的模塊,最重要的因素在於,這個模塊對於外部的其它模塊而言,是否隱藏其內部數據和其它實現細節。
    • 儘可能地使每個類或者成員不被外界訪問
    • 對於包內頂層地類和接口,要麼包級私有要麼public, 一旦public開發者有責任永遠支持它
    • 如果包級私有地頂層類只被包內的一個類用到,要考慮使它成為那個類的私有嵌套類,使可訪問範圍更小
    • 實例域和靜態域絕不能是公有的
    • 對於final數組域可以這樣控制權限
    <code>private static final Thing[] PRIVATE_VALUES = {...};
    public static final List<thing> VALUES = Collections.unmodifiableList(Arrays.asList(PRIVATE_VALUES));
    /<thing>/<code>

    用函數表示策略, 策略抽象成接口,如果實現接口的具體策略只使用一次,用匿名類,否則應該定義一個靜態final函數, 返回類型為策略接口

    <code>public static > Comparator<map.entry>> comparingByValue() {
    return (Comparator<map.entry>> & Serializable)
    (c1, c2) -> c1.getValue().compareTo(c2.getValue());
    }
    /<map.entry>/<map.entry>
    /<code>
    • 靜態成員類是外圍類的一個成員, 常見用法是作為共有類的輔助類, 比如一個類裡面有個靜態枚舉類;
    • 非靜態成員類的每個實例都隱含著與外部類一個外部實例相關聯(影響外部類的垃圾回收)。當非靜態成員類的實例被創建的是時候,它和外圍實例之間的關聯關係也隨之建立起來&不能被修改
    • 每當編寫方法和構造器的時候,應該考慮他它的參數有哪些限制,應該把限制寫到文檔中,並在方法的開頭出加上限制邏輯,私有方法assert斷言
    • 我們要保護性的去設計程序,如果API設計的不好,客戶端很容易誤解,並導致不可預期的行為,所以編寫面對客戶的不良行為時仍能保持健壯的類,這是非常值得投入時間去做的事情。要注意是否允許調用者修改其內部的組件,
    • 關於方法簽名的設計:
    • 方法名稱儘量要風格一致,並選擇大眾認可的名稱
    • 類的方法設計太多,會使類難以學習,使用,文檔化,測試以及維護
    • 避免過長的參數列表,目標參數個數4個以內,太長不好記,容易亂序
    • 拆分參數子集為多個方法入參
    • 將多個頻繁出現的參數序列封裝成靜態成員類,並考慮使用builder方法構建
    • 對於參數類型,要優先使用接口而不是類
    • 對於boolean參數,要優先使用兩個元素的枚舉類型,例如在一個靜態工廠中newInstance(PayType.WX)
    • 易於閱讀和編寫
    • 易於擴展
    • 枚舉常量內易於增加方法
    • 對於多個具有相同參數數目的方法來說,應該儘量避免重載方法,重載是編譯期確定調用哪個重載方法,覆寫是在運行時
    • 返回類型為數組或集合的方法應該返回一個零長度的數組或者集合
    • 為了正確地編寫API文檔,必須在每個被導出的方法,類,接口,構造器和字段聲明之前增加文檔註釋
    • 方法的文檔註釋應該描述它與客戶端的約定,而不是說這個方法是怎麼幹到的
    • 前置條件 後置條件? 副作用 以及方法的線程安全性
    <code>@param @return @throw (if xxx then yyy) @code 
     @code 

    /<code>
    • 文檔註釋的第一句話,是該註釋所屬元素的概要描述
    • 要使局部變量的作用域最小化,最佳實踐是在第一次使用它的地方聲明它

    異常

    • 只針對異常的情況才使用異常, 不能利用異常來做其它投機取巧的邏輯
    • 對於可恢復的情況且允許調用者能夠進行適當的恢復使用受檢異常, 其它異常使用運行時異常
    • 優先使用jdk裡的標準的異常,對於這些常見的可重用的異常會降低API的學習成本
    • .更高層的實現應該捕獲低層的異常, 同時拋出可以按照高層抽象進行解釋的異常,叫做異常轉譯, 這樣避免了方法拋出的異常與它所執行的任務沒有明顯的聯繫, 讓人不知所措
    <code>
    try {
    } catch(LowerLevelException e) {
    \tthrow new HigherLevelException(...);
    }
    /<code>
    • 底層的異常被傳到高層的異常, 高層的異常提供訪問方法(Throwable.getCause)來獲取底層的異常
    • 不過我們應該在底層方法調用的時候儘量確保它們會執行成功,從而避免它們拋出異常,比如通過嚴格的檢查高層傳遞到底層的參數。次選方案是,讓高層悄悄的繞開異常, 將高層方法的調用者與底層問題隔離起來。(底層catch異常打錯誤日誌)
    • 一般而言,失敗的方法調用應該使對象保持在被調用之前的狀態異常要打印關鍵信息,禁止忽略異常


    分享到:


    相關文章: