Effictive Java 不徹底學習筆記
這裏只是基於本身的理解作的一些記錄, 一些對本身來講已是一種常識, 將再也不列出.
因爲做者是sun jdk collection framework的主要貢獻者, 所以書中不少的effective rule, 大多數是對collection framework實現的一個總結(不少例子都是拿Collection中的東東在說事兒), 站在JDK API的高度來思考的, 這些考慮是很是嚴謹的, 而咱們平時的開發可能不會注意到這些細節, 或者可能沒有做者所說的場景, 所以能夠根據須要選擇性的理解.
第5條 消除過時的對象引用
這裏講到了一個由於過時引用致使的內存泄漏的問題, 而平時咱們也很難注意到這個問題, 這個例子是一個Stack的實現, 在Stack內部用一個數組來緩存堆棧中的全部元素, 在其pop方法裏面這樣寫:
Java代碼
- public Object pop(){
- if (size == 0){
- throw new EmptyStackException();
- }
- return elements[--size];
- }
當彈出一個元素的時候, elements其實還存在對這個元素的引用. 正確的作法應該是
Java代碼
- Object result = elements[--size];
- element[size]=null;
- return result;
可是對於java來講, 每次對再也不使用的對象手工強制設置爲null這不是一種好的處理方式, 之因此要這麼作的前提是:須要對象本身來管理內存, 即對於再也不使用的對象, 須要明確的告訴垃圾收集器再也不使用, 不然就會形成內存泄漏.
第8條 改寫equals時老是要改寫hashCode
hashCode對HashMap這種須要用到HashCode的容器具備很是重要的意義.
這裏做者舉了個電話號碼的例子, 若是一個電話號碼包括區號, 號碼和分機號, 對象爲PhoneNumer("0571", "88155188", "1234"), 可是未實現hashCode()方法;在hashMap中用PhoneNumber做爲key, 用戶名做爲value這樣保存, hashMap.put(new PhoneNumer("0571", "88155188", "1234"),"jenny"), 若是用PhoneNumer("0571", "88155188", "1234")到hashMap中取得的倒是null
計算hashCode, 17是一個不錯的初始值, 37是一個不錯的乘數因子, 計算hashcode, 通常採用這種寫法:
Java代碼
- result = 17;
- result = result * 37 + property1 hashcode;
- result = result * 37 + property2 hashcode;
- result = result * 37 + property3 hashcode;
第12條 使類和成員的可訪問能力最小化
若是須要聲明一個共有的靜態final數組時, 應該將其聲明爲私有, 而後提供一個非可變的 final List, 好比下面的作法:
Java代碼
- private static final type[] PRIVATE_VALUES = {...};
- public static final List VALUES = Collections.unmodiableList(Arrays.asList(PRIVATE_VALUES));
還有一個作法是經過一個公共的方法, 而後返回數組的clone, 即保護性拷貝
第14條 複合優於繼承
這是一個老生常談的話題, 繼承的優勢是有利於重用, 可是卻帶來安全的問題.
在一個包內的繼承是能夠接受的, 由於包內的繼承幾乎能夠被認爲是在同一個程序員的控制範圍以內, 還有隻有那些專門爲繼承而設計的基類, 這樣的繼承基本上也是安全的, 而對於普通的具體類使用繼承的一個問題就是破壞了原有類的封裝性, 從而給程序埋下了bug的種子.
這裏做者舉了繼承HashSet實現計數功能的例子, 計數針對add和addAll方法來進行, 可是addAll內部是調用add方法來添加元素的, 而子類的計數功能必須依賴父類這個並無承諾的實現細節, 若是父類addAll方法的實現細節發生改變, 將致使子類的計數功能失敗. 爲了保證子類的計數功能不依賴父類的實現, 一般不得已的作法是重寫父類的相關代碼.
盲目的繼承會存在各類問題, 而優於繼承的複合卻能克服這些問題, 做者針對上面的問題用複合來舉例子, 讓用於計數的HashSet實現Set接口, 內部持有一個真正的HashSet實例, 而後將全部的方法轉發到HashSet實例上, 這樣整個計數Set徹底不用依賴HashSet內部的實現細節, 除了帶來健壯性外, 還帶來了靈活性
只有兩個類A和B之間存在is-a關係的時候(B確實是一個A), 才應該採用繼承.
第15條
好的API文檔應該描述一個方法作了什麼工做, 而並不是描述它是如何作到的.
構造函數最好不要調用(或依賴)可被改寫的方法, 這個主要是處於初始化的考慮, 由於父類的構造函數會在子類構造函數初始化以前被調用, 而被改寫的方法又依賴於子類是否初始化, 這樣致使初始化失敗.
第16條 接口優於抽象類
抽象類主要處理具備層次關係的架構(在這種場景下, 抽象類可能優於接口了), 而接口則能夠不受這個應用場景的限制, 他能夠爲類mixin新的功能.
第17條 接口只能用於定義類型
這個主要是做者針對在接口中定義常量的作法的控訴, 我以爲沒那麼嚴重, 只要沒有讓須要使用常量的類繼承常量接口便可, 這樣常量接口跟在類中定義常量沒有區別.
第18條 優先考慮靜態成員類
若是你聲明的成員類不要求訪問外圍實例, 那麼請記住把static修飾符放到成員類的聲明中.
非靜態成員類的每個實例都隱含的與外圍類的一個實例緊密的關聯在一塊兒, 這樣非靜態成員類的實例才能調用外圍類實例的方法.
做者舉了幾個Collection中的例子.一個是List中的Iterator, Map中的keySet, valueSet, 由於他們本身都不緩存數據, 須要訪問外部類實例中數據, 所以必須是非靜態的內部類.而對於HashMap中的每一對鍵值(key-value)的包裝類Entry來講, 雖然一個Entry須要與一個具體的Map關聯, 可是它的getValue, setValue等方法並不須要訪問外部類HashMap, 而是在建立每個Entry的時候就已經從Map中拿到key, value並塞到Entry中去了.
對於匿名類, 主要用來封裝那些很是簡短的邏輯, 若是代碼超過20行, 則採用匿名類就不是很理想了.
第28條 爲導出的API編寫文檔
爲API編寫的JavaDoc文檔應該簡潔的描述它與客戶之間的約定, 應該說明它作了什麼, 而不是描述它如何作的, 若是是方法的話, 最好描述它的前置條件(調用這個方法必須知足的條件)和後置條件(成功調用以後, 哪些條件必須知足), 通常狀況下, @throws中隱含的非檢查異常就是前置條件 ,由於每個非檢查異常就意味着一個不知足的條件, 有時候也在@param中來講明前置條件.
此外還應該描述方法的反作用, 也就是對其餘相關內容形成的影響.
在寫方法的JavaDoc的時候, 通常@param和@return後面應該接一個名詞, 而@throws 應該以"若是"開頭來代表異常將在什麼狀況下拋出來.
對於方法, 類, 屬性的描述有一些不成爲的規律:
對於方法和構造函數的概要描述, 應該是一個動詞短語, 好比
ArrayList(int)描述爲:用指定的初始容量構造一個空的列表
Collection.size() 返回集合中元素的數目
對於類, 接口和屬性, 概要描述應該是一個名詞短語,好比
TimerTask:能夠被一次調度的一項任務.
第29條 將局部變量的做用域最小化
這裏說明了for循環比while要好的例子.
由於while須要在循環以外定義變量, 而for循環則被包含在了循環內部, 這樣就能夠避免那些偷懶拷貝代碼帶來隱患
第30條 瞭解和使用庫
主要是爲了說明避免重複發明輪子.
每一個程序員應該熟悉java.lang, java.util甚至java.io類庫
另外apache的一些庫也是咱們每一個人應該熟悉的:)
第31條 若是要求精確計算, 請避免使用float和double
就是說用float, double來進行涉及到小數的計算是不靠譜的, 應該轉換成int和long或者使用BigDecimal.
第39條 只針對不正常的條件才使用異常
異常永遠不能用於正常的控制流
第40條 對於可恢復的條件使用被檢查的異常, 對於程序錯誤使用運行時異常
其實咱們的程序中, 拋出的異常絕大部分是不可恢復的.所以應該避免使用被檢查的異常
第42條 儘可能使用標準的異常
IllegalStateException, 這個異常應該屬於經常使用的異常, 若是在一個對象被正確初始化以前被調用, 那麼應該拋出這個異常.
通常來講, 全部錯誤方法的調用能夠歸結爲非法的參數和非法的狀態, 可是咱們通常不能籠統的拋出IllegalArgumentException, 而應該使用更明確說明異常緣由的異常, 好比NullPointerException, IndexOutOfBoundsException等.
第48條 對共享可變數據的同步訪問
這裏舉了個很好的使用同步的例子:使用雙檢查, 將同步縮小在最小範圍
延遲初始化, 通常會這樣寫:
Java代碼
- private static Foo foo = null;
- public synchronized static Foo getFoo(){
- if (foo == null){
- foo = new Foo();
- }
- return foo;
- }
可是能夠將同步進一步縮小, 這裏採用雙檢查模式, 在foo初始化以後, 不用再被同步(可是可能存在部分初始化的問題):
Java代碼
- public static Foo getFoo(){
- if (foo == null){
- synchronized(Foo.class){
- if (foo == null){
- foo = new Foo();
- }
- }
- }
- return foo;
- }
可是更精妙的作法是徹底不用同步:
Java代碼
- private static class FooHolder{ static final Foo foo = new Foo();}
- public static Foo getFoo(){return FooHolder.foo;}
他主要利用了java語言中的"只有當一個類被用到的時候才被初始化"
歡迎關注本站公眾號,獲取更多信息