1. 程式人生 > 實用技巧 >《Effective Java 第三版》——精華總結

《Effective Java 第三版》——精華總結

程式碼:

https://files.cnblogs.com/files/cx2016/effective-java-3e-source-code.zip

1.

總之,靜態工廠方法和公共構造方法都有它們的用途,並且瞭解它們的相對優點是值得的。通常,靜態工廠更可取,因此避免在沒有考慮靜態工廠的情況下提供公共構造方法。

2.

總而言之,當設計類的構造方法或靜態工廠的引數超過幾個時,Builder模式是一個不錯的選擇,特別是如果許多引數是可選的或相同型別的。客戶端程式碼比使用伸縮構造方法(telescoping constructors)更容易讀寫,並且builder比JavaBeans更安全。

3.

單一元素列舉類通常是實現單例的最佳方式

4.

工具類(一組靜態方法和靜態屬性):可以通過包含一個私有構造方法來實現類的非例項化 5.

總之,不要使用單例或靜態的實用類來實現一個類,該類依賴於一個或多個底層資源,這些資源的行為會影響類的行為,並且不讓類直接建立這些資源。相反,將資源或工廠傳遞給構造方法(或靜態工廠或builder模式)。這種稱為依賴注入的實踐將極大地增強類的靈活性、可重用性和可測試性。

6.

這個條目不應該被誤解為暗示物件建立是昂貴的,應該避免建立物件。 相反,使用構造方法建立和回收小的物件是非常廉價,構造方法只會做很少的顯示工作,,尤其是在現代JVM實現上。 建立額外的物件以增強程式的清晰度,簡單性或功能性通常是件好事。

相反,除非池中的物件非常重量級,否則通過維護自己的物件池來避免物件建立是一個壞主意。物件池的典型例子就是資料庫連線。建立連線的成本非常高,因此重用這些物件是有意義的。但是,一般來說,維護自己的物件池會使程式碼混亂,增加記憶體佔用,並損害效能。現代JVM實現具有高度優化的垃圾收集器,它們在輕量級物件上輕鬆勝過此類物件池。

這個條目的對應點是針對條目 50的防禦性複製(defensive copying)。 目前的條目說:“當你應該重用一個現有的物件時,不要建立一個新的物件”,而條目 50說:“不要重複使用現有的物件,當你應該建立一個新的物件時。”請注意,重用防禦性複製所要求的物件所付出的代價,要遠遠大於不必要地建立重複的物件。 未能在需要的情況下防禦性複製會導致潛在的錯誤和安全漏洞;而不必要地建立物件只會影響程式的風格和效能。

7.

當一個類自己管理記憶體時,程式設計師應該警惕記憶體洩漏問題

8.

總之,除了作為一個安全網或者終止非關鍵的本地資源,不要使用Cleaner機制,或者是在Java 9釋出之前的finalizers機制。即使是這樣,也要當心不確定性和效能影響。

9.

結論明確:在處理必須關閉的資源時,使用try-with-resources語句替代try-finally語句。 生成的程式碼更簡潔,更清晰,並且生成的異常更有用。 try-with-resources語句在編寫必須關閉資源的程式碼時會更容易,也不會出錯,而使用try-finally語句實際上是不可能的。

10.

總之,除非必須:在很多情況下,不要重寫equals方法,從Object繼承的實現完全是你想要的。 如果你確實重寫了equals 方法,那麼一定要比較這個類的所有重要屬性,並且以保護前面equals約定裡五個規定的方式去比較。

11.

總之,每次重寫equals方法時都必須重寫hashCode方法,否則程式將無法正常執行。你的hashCode方法必須遵從Object類指定的常規約定,並且必須執行合理的工作,將不相等的雜湊碼分配給不相等的例項。如果使用第51頁的配方,這很容易實現。如條目 10所述,AutoValue框架為手動編寫equals和hashCode方法提供了一個很好的選擇,IDE也提供了一些這樣的功能。

12.

回顧一下,除非父類已經這樣做了,否則在每個例項化的類中重寫Object的toString實現。 它使得類更加舒適地使用和協助除錯。 toString方法應該以一種美觀的格式返回物件的簡明有用的描述。

13.

考慮到與Cloneable介面相關的所有問題,新的介面不應該繼承它,新的可擴充套件類不應該實現它。 雖然實現Cloneable介面對於final類沒有什麼危害,但應該將其視為效能優化的角度,僅在極少數情況下才是合理的(條目67)。 通常,複製功能最好由構造方法或工廠提供。 這個規則的一個明顯的例外是陣列,它最好用 clone方法複製。

14.

總而言之,無論何時實現具有合理排序的值類,你都應該讓該類實現Comparable介面,以便在基於比較的集合中輕鬆對其例項進行排序,搜尋和使用。 比較compareTo方法的實現中的欄位值時,請避免使用"<"和">"運算子。 相反,使用包裝類中的靜態compare方法或Comparator介面中的構建方法。

15.

總而言之,應該儘可能地減少程式元素的可訪問性(在合理範圍內)。 在仔細設計一個最小化的公共API之後,你應該防止任何散亂的類,介面或成員成為API的一部分。 除了作為常量的公共靜態final屬性之外,公共類不應該有公共屬性。 確保public static final屬性引用的物件是不可變的。

16.

總之,公共類不應該暴露可變屬性。 公共累暴露不可變屬性的危害雖然仍然存在問題,但其危害較小。 然而,有時需要包級私有或私有內部類來暴露屬性,無論此類是否是可變的。

17.

總而言之,堅決不要為每個屬性編寫一個get方法後再編寫一個對應的set方法。除非有充分的理由使類成為可變類,否則類應該是不可變的。 不可變類提供了許多優點,唯一的缺點是在某些情況下可能會出現效能問題。 你應該始終使用較小的值物件(如PhoneNumberComplex),使其不可變。 (Java平臺類庫中有幾個類,如java.util.Datejava.awt.Point,本應該是不可變的,但實際上並不是)。你應該認真考慮建立更大的值物件,例如StringBigInteger,設成不可改變的。 只有當你確認有必要實現令人滿意的效能(條目 67)時,才應該為不可改變類提供一個公開的可變夥伴類。

對於一些類來說,不變性是不切實際的。如果一個類不能設計為不可變類,那麼也要儘可能地限制它的可變性。減少物件可以存在的狀態數量,可以更容易地分析物件,以及降低出錯的可能性。因此,除非有足夠的理由把屬性設定為非 final 的情況下,否則應該每個屬性都設定為 final 的。把本條目的建議與條目15的建議結合起來,你自然的傾向就是:除非有充分的理由不這樣做,否則應該把每個屬性宣告為私有final的

構造方法應該建立完全初始化的物件,並建立所有的不變性。 除非有令人信服的理由,否則不要提供獨立於構造方法或靜態工廠的公共初始化方法。 同樣,不要提供一個“reinitialize”方法,使物件可以被重用,就好像它是用不同的初始狀態構建的。 這樣的方法通常以增加的複雜度為代價,僅僅提供很少的效能優勢。

18.

總之,繼承是強大的,但它是有問題的,因為它違反封裝。 只有在子類和父類之間存在真正的子型別關係時才適用。 即使如此,如果子類與父類不在同一個包中,並且父類不是為繼承而設計的,繼承可能會導致脆弱性。 為了避免這種脆弱性,使用合成和轉發代替繼承,特別是如果存在一個合適的介面來實現包裝類。 包裝類不僅比子類更健壯,而且更強大。

19.

總之,設計一個繼承類是一件很辛苦的事情。 你必須文件說明所有的自用模式,一旦你文件說明了它們,必須承諾為他們的整個生命週期。 如果你不這樣做,子類可能會依賴於父類的實現細節,並且如果父類的實現發生改變,子類可能會損壞。 為了允許其他人編寫高效的子類,可能還需要匯出一個或多個受保護的方法。 除非你知道有一個真正的子類需要,否則你可能最好是通過宣告你的類為final禁止繼承,或者確保沒有可訪問的構造方法。

20.

總而言之,一個介面通常是定義允許多個實現的型別的最佳方式。 如果你匯出一個重要的介面,應該強烈考慮提供一個骨架的實現類。 在可能的情況下,應該通過介面上的預設方法提供骨架實現,以便介面的所有實現者都可以使用它。 也就是說,對介面的限制通常要求骨架實現類採用抽象類的形式。

21.

準則是清楚的。 儘管預設方法現在是Java平臺的一部分,但是非常悉心地設計介面仍然是非常重要的。 雖然預設方法可以將方法新增到現有的介面,但這樣做有很大的風險。 如果一個介面包含一個小缺陷,可能會永遠惹怒使用者。如果一個介面嚴重缺陷,可能會破壞包含它的API。

因此,在釋出之前測試每個新介面是非常重要的。 多個程式設計師應該以不同的方式實現每個介面。 至少,你應該準備三種不同的實現。 編寫多個使用每個新介面的例項來執行各種任務的客戶端程式同樣重要。 這將大大確保每個介面都能滿足其所有的預期用途。 這些步驟將允許你在釋出之前發現介面中的缺陷,但仍然可以輕鬆地修正它們。雖然在介面被髮布後可能會修正一些存在的缺陷,但不要太指望這一點

22.

總之,介面只能用於定義型別。 它們不應該僅用於匯出常量。

23.

總之,標籤類很少有適用的情況。 如果你想寫一個帶有明顯標籤屬性的類,請考慮標籤屬性是否可以被刪除,而類是否被類層次替換。 當遇到一個帶有標籤屬性的現有類時,可以考慮將其重構為一個類層次中。

24.

回顧一下,有四種不同的巢狀類,每個都有它的用途。 如果一個巢狀的類需要在一個方法之外可見,或者太長而不能很好地適應一個方法,使用一個成員類。 如果一個成員類的每個例項都需要一個對其宿主例項的引用,使其成為非靜態的; 否則,使其靜態。 假設這個類屬於一個方法內部,如果你只需要從一個地方建立例項,並且存在一個預置型別來說明這個類的特徵,那麼把它作為一個匿名類; 否則,把它變成區域性類。

25.

這個教訓很清楚:永遠不要將多個頂級類或介面放在一個原始檔中。 遵循這個規則保證在編譯時不能有多個定義。 這又保證了編譯生成的類檔案以及生成的程式的行為與原始檔傳遞給編譯器的順序無關。

26.

總之,使用原始型別可能導致執行時異常,所以不要使用它們。 它們僅用於與泛型引入之前的傳統程式碼的相容性和互操作性。 作為一個快速回顧,Set<Object>是一個引數化型別,表示一個可以包含任何型別物件的集合,Set<?>是一個萬用字元型別,表示一個只能包含某些未知型別物件的集合,Set是一個原始型別,它不在泛型型別系統之列。 前兩個型別是安全的,最後一個不是。

27.

總之,未經檢查的警告是重要的。 不要忽視他們。 每個未經檢查的警告代表在執行時出現ClassCastException異常的可能性。 盡你所能消除這些警告。 如果無法消除未經檢查的警告,並且可以證明引發該警告的程式碼是安全型別的,則可以在儘可能小的範圍內使用@SuppressWarnings(“unchecked”)註解來禁止警告。 記錄你決定在註釋中抑制此警告的理由。

28.