《Effective Java》學習筆記(一)——建立和銷燬物件
優點:
- 具名——靜態工廠方法與名稱
- 環保——不必在每次呼叫的時候都建立一個新物件;
- 多子——可以返回原返回型別的任何子型別的物件;
常見的靜態工廠方法名:
- valueOf/of——型別轉換,返回的例項和入參具有相同的值;
- getInstance——返回一個預先建立好的例項;
- newInstance——返回一個新的例項;
- getType——像getInstance一樣,但是在工廠方法處於不同的類中的時候使用;
- newType——像newInstance一樣,但是在工廠方法處於不同的類中的時候使用。
當一個類沒有提供靜態工廠方法的時候,我們才需要使用工廠模式。
缺點:
- 靜態工廠方法的主要缺點在於,類如果不含公有的或者受保護的構造器,就不能被子類化;
- 靜態工廠方法的第二個缺點在於,它們與其他的靜態方法實際上沒有任何區別;
遇到多個構造器引數時要考慮用構建器
靜態工廠和構造器有個共同的侷限性:它們都不能很好地擴充套件到大量的可選引數。兩種常見方案:
重疊構造器模式可行,但是當有很多引數的時候,客戶端程式碼會很難編寫,並且仍然較難以閱讀。
JavaBeans模式在構造過程中可能處於不一致的狀態,且阻止了把類做成不可變的可能。
第三種就是構建者模式(Builder模式):不直接生成想要的物件,而是讓客戶端利用所有必要的引數呼叫構造器(或者靜態工廠),得到一個builder物件。然後客戶端在builder物件上呼叫類似setter的方法,來設定每個相關的可選引數。最後客戶端呼叫無參的build方法來生成不可變的物件。這個builder是它構建的類的靜態成員類。
不足:為了建立物件,必須先建立它的構建器;Builder模式還比重疊構造器模式更加冗長,因為它只在有很多引數的時候才使用。
如果類的構造器或者靜態工廠中具有多個引數,設計這種類時,Builder模式就是中不錯的選擇,特別是當大多數引數都是可選的時候。
用私有構造器或者列舉型別強化Singleton屬性
傳統單例實現存在的一個問題是一旦實現了序列化介面,那麼它們不再保持單例了,因為readObject()方法一直返回一個新的物件就像java的構造方法一樣,可以通過使用readResolve()方法來避免:
//readResolve to prevent another instance of Singleton private Object readResolve(){ return INSTANCE; }
一個使用列舉型別來實現的單例模式:
// 需要應用單例模式的資源,具體可以表現為網路連線,資料庫連線,執行緒池等 class Resource{ } public enum SomeThing { INSTANCE; private Resource instance; private SomeThing() { instance = new Resource(); } public Resource getInstance() { return instance; } }
獲取資源的方式很簡單,只要SomeThing.INSTANCE.getInstance()即可獲得所要例項。因為在列舉中明確了構造方法為私有,在我們訪問列舉例項時會執行構造方法,同時,每個列舉例項都是static final型別的,也就表明只能被例項化一次。
通過私有構造器強化不可例項化的能力
工具類(utility class)不希望被例項化,例項沒有任何意義。在缺少顯式構造器的時候,編譯器會自動提供一個公有的、無參的預設構造器。所以我們只要讓這個類包含私有構造器,它就不能被例項化了。當然副作用就是,這個類就不能被子類化了。所有的構造器都必須顯式或隱式地呼叫超類構造器,在這種情形下,子類就沒有可訪問的超類構造器課呼叫了。
避免建立不必要的物件
對於同時提供了靜態工廠方法和構造器的不可變類,通常可以使用靜態工廠方法而不是構造器,以避免建立不必要的物件。
除了重用不可變的物件之外,也可以重用哪些已知不會被修改的可變物件。
當你應該重用現有物件的時候,請不要建立新的物件。
消除過期的物件引用
過期引用是指永遠也不會再被解除的引用。如果一個物件引用被無意識地保留起來了,那麼,垃圾回收機制不僅不會處理這個物件,而且也不會處理被這個物件所引用的所有其他物件。即使只有少量的幾個物件引用被無意識地保留下來,也會有許許多多的物件被排除在垃圾回收機制之外,從而對效能造成潛在的重大影響。
這類問題的修復方法是:一旦物件引用已經過期,只需清空這些引用即可。
清空過期引用的另一個好處是:如果它們以後又被錯誤的解除引用,程式就會立即丟擲NullPointerException異常,而不是悄悄地錯誤執行下去。
清空物件引用應該是一種例外,而不是一種規範行為。消除過期引用最好的方法是讓包含該引用的變數結束其生命週期。
除了過期引用,記憶體洩漏的另一個常見來源是快取。可能的解決方案:
- 如果正好要實現這樣的快取:只要在快取之外存在對某個項的鍵的引用,該項就有意義,那麼就可以用WeakHashMap代表快取;當快取中的項過期以後,它們就會自動被刪除。只有當所要的快取項的生命週期是由該鍵的外部引用而不是由值決定時,WeakHashMap才有用處。
- “快取項的生命週期是否有意義”並不容易確定,隨著時間的推移,其中的項會變得越來越沒有價值。在這種情況下,快取應該時不時地清除掉沒用的項。這項清除工作可以由一個後臺執行緒(Timer或者ScheduledThreadPoolExecutor)來完成,或者也可以在給快取新增新條目的時候順便進行清理。
記憶體洩漏的第三個常見來源是監聽器和其他回撥。如果實現了一個API,客戶端在這個API中註冊回撥,卻沒有顯式地取消註冊,那麼除非你才去某些動作,否則它們就會積聚。確保回撥立即被當作垃圾回收的最佳方法是隻儲存它們的弱引用。
避免使用終結方法
終結方法的缺點在於不能保證會被及時地執行。從一個物件變得不可到達開始,到它的終結方法被執行,所花費的這段時間是任意長的。Java語言規範不僅不保證終結方法會被及時地執行,而且根本就不保證它們會被執行。所以,不應該依賴終結方法來更新重要的持久狀態。
如果類的物件中封裝的資源(例如檔案或者執行緒)確實需要終止,應該提供一個顯式的終止方法,並要求該類的客戶端在每個例項不再有用的時候呼叫這個方法。顯式終止方法的典型例子InputStream、OutputStream和java.sql.Connection上的close方法。顯式的終止方法通常與try-finally結構結合起來使用,以確保及時終止。