天天看點

Effective Java讀書筆記、感悟——1.建立和銷毀對象

    每天看點Effective Java,挺好的,至今覺着Java小菜,多學基礎知識,喜歡因為做喜歡的事忘記時間的感覺。

    不多吐槽了。直奔主題,這裡隻是筆記和一些感觸,選取了一些我熟悉的類和方法舉個例子,因為很久了做Android比較多,我可能會選擇一些Android的類庫說明android相關的設計理念,對于常用到的就不再舉例,一時想不到有些,也不必要非要找出執行個體,自己做時候注意就好了。如果細緻了解建議看原文,原文中提到了一些例子,除了很重要的、以前忽略的這裡都不再重複。

一:考慮靜态工廠方法代替構造器 ->靜态工廠方法,不全同設計模式中的工廠模式。 ->靜态工廠方法可以解決構造函數隻能參數類型順序不同的問題,比如可以兩組相同的序列可以用方法名表示不同的含義。 ->可以傳回子類型,類似工廠模式了。到這裡可以屏蔽子類,如針對不同參數傳回不同子類優化性能,如果後來發展無法滿足需求了,大可增加或删除子類,而構造方法不行。 ->對于編碼階段禁忌第一反應是構造方法。 ->靜态工廠方法缺點,無可繼承構造方法時無法子類化,還有在java doc中和其他方法沒有差別,而構造方法會單獨列出。

二:遇到多個構造器參數時考慮用構造器 ->建構器builder。這裡很有代表性的就是Android中Dialog的Builder,詳見API,提供了先傳入參數後構造Dialog的方法。 ->多個參數的解決方案中: 重疊構造器包含了使用者不期望的預設值。Java bean非線程安全,可能處于不一緻狀态,且阻止了把類做成不可變的可能。 ->建構器使用抽象工廠解決了兩者,且可以在對象域(解釋下,産生對象的方法)進行限制檢查,違反抛出illegalstateexception,或在每個setter進行。 ->Java中傳統的抽象工廠實作是Class對象,用newInstance方法充當build方法的一部分,這種用法隐含着許多問題。因為newInstance方法總是企圖調用類的無參數構造器,這個構造器甚至可能根本不存在。如果類沒有可以通路的無參構造器不會出現編譯錯誤,但是client code中必須catchInstantiationException or IllegalAccessException和一切因無參數構造器帶來的問題,即使無throws子句。Class newInstantce破壞了編譯時的異常檢查,而Builder可以彌補。 ->缺點:需要多建立一個構造器,代碼顯得冗長,一般用于多餘4個參數調用,但如果你将來有添加參數的可能,那麼請用構造器,因為同時存在這些就比較不協調了,對于API的設計來講是非常不合理的。

三:用私有構造器或枚舉類型強化Singleton屬性 Singleton屬性一般采取的兩種方式: 1. 靜态final instance 2. 私有final instance,提供靜态getInstance方法傳回,我們一般采取這種方式,因為它可以保證在API不便的情況下標明是否為Singleton或修改為其他形式。 這裡提到這兩種方式主要要說明兩個沒注意的問題:反射和序列化 ->反射:這種方式反射都可以通路到構造方法,可以對構造方法做修改,當産生第二個執行個體抛出異常。 ->序列化:僅僅普通方法實作接口是不夠的,以為為了保證Singleton,必須要聲明所有執行個體域都是瞬時的,并提供一個readResolve方法。否則存在的問題是每次反序列化一個序列化執行個體,都會建立一個新的執行個體。 private Object readResolve(){       return instance; }

書中提到了1.5增加枚舉後的第三種方法, public enum Elvis{      INSTANCE;

     methods...... } 此方法直接解決以上兩個問題。Android很重要的就是工具庫編寫的DB工具類使用了Singleton生成Android自身的dbhandler來處理,雖然不序列化,但是反射是個很嚴重的問題,畢竟是工具包,是以快點重構吧親。同樣,也許你在HttpHandler中也需要用到。如果沒有制作這兩個包,建議使用,因為可重用性非常高,也需要解決很多問題,幾乎每個項目都使用,同樣,保證包的使用的穩定性,不多說了,下一條。

四:通過私有構造器強化不可執行個體化的能力 ->對隻有靜态方法和域的類,主要用于把基本類型的值或資料類型上的相關方法組織起來(Math,Arrays),可以通過Collections的方法把實作特定接口的對象上的驚天方法組織起來,可以利用這種類把final類上的方法組織起來,以取代擴充該類的做法。 ->此工具類是不希望被執行個體化的,執行個體化對他麼有任何意義。然後我們如果不提供構造器,jvm會自己提供,那還是會被執行個體化,那麼我們隻要在類中提供一個私有的構造器就可以了,并添加注釋說明。 這樣帶來的問題是不能子類化,因為子類要求要求super父類的構造函數。

五:避免建立不必要的對象 ->若一個方法頻繁調用且每次生成相同的内部執行個體,可以作為static,如Map的keyset。 ->維護自己的對象池來避免重複建立對象不是很好的做法,除非對象是非常重量級的,Object pool也增加了記憶體占用。

六:消除過期的對象引用 存在過期引用會導緻無法GC,但清空對象引用應該是一種例外,而不是一種規範行為。 一下三種情況要考慮會發生記憶體洩露: 1.類自己申請記憶體管理 2.緩存,易忘記管理,如WeakHashMap可以自動處理沒有被外部引用的緩存項。一般利用背景線程定時清理,也可以類似LinkedHashMap使用removeEldestEntry在添加條目時清理。對于複雜的緩存,必須直接使用java.lang.ref 3.監聽器和其他回調,回調此時可以做成弱引用。

七:避免使用終結方法 這裡先說一下finalize的執行方式,再來說effective java中學到的東西。JVM采用跟搜尋算法進行GC對象選擇,這裡不細說,JVM發現需要回收的對象(當然直覺的來看就是沒有引用的對象了)會做一次标記并進行一次篩選,篩選的條件就是此對象是否要執行finalize方法(對象未覆寫或finalize已被JVM執行過一次都被視為不需要執行),需要執行的放在F-Queue中,并稍後由一條由虛拟機自動建立的、低優先級的Finalizer線程去執行,但執行僅指虛拟機會觸發這個方法,但不承諾會等待此方法運作結束,原因很簡單,不可能因為等待這一個程序而使得其他要回收的資源一直等待,這樣會導緻GCC崩潰。當然一般也沒有人會去這樣做,畢竟教材沒有這樣教的,這裡effective java中提到了兩個用途很重要去掌握,回收資源一般作為一種顯示的stop方法。finalize方法的作用: 1. 當使用者沒有調用stop,這時候在finilize中回收資源總比不回收強,相當于補充一個安全網。(Android中最典型的是MediaPlayer,如果不回收資源将會帶來很大的問題,可以檢視Android确實是這樣做的,它調用的方法如下: protected void finalize() { native_finalize(); }  具體含義不是這裡應該繼續讨論的問題,但是需要關注的是如果去做,這裡MediaPlayer做的不是realse一樣的内容,實際需要考慮finalize執行的問題,它不能耗時太長且不保證執行) 2. 普通對象通過本地方法委托給一個本地對象(本地對等體),因為本地對等體不是普通對象,gC不知道它,當本地對等體不擁有關鍵資源時finilize是最合适的工具。 需要注意的是,子類如果不super父類的finalize那麼不會執行父類的代碼,為了防止子類惡意不調可以使用放入一個匿名類,建立一個終結方法守衛者,即當他finalize時會釋放外面的資源,與子類調用與否無關。但是此方法不推薦使用,因為方法的本意隻是為了讓c++程式員更快習慣java的編碼而做的妥協,并不是java的析構函數