創建和銷毀對象
第一條:考慮用靜態工廠方法代替構造器
當該類需要返回不同的實例(子類實例),或者唯一一個實例(單例)時,應考慮用靜態工廠方法代替構造器。
靜態工廠方法的一些慣用名稱:
- valueOf 返回的實例與參數有相同的值,相當于類型轉換
- of valueOf的簡化
- getInstance 返回的實例是通過參數類描述的,但一般不具有與參數同樣的值。對于Singleton來說,無參數,返回唯一一個實例。
- newInstance 類似于getInstance,但每次返回新的實例
- getType 在工廠方法處于不同的類時使用,Type指返回類型的類型
- newType 同上
第二條:遇到多個構造器參數時要考慮用構建器
當創建對象面臨多個可選參數時(即,構造器有多個可選的參數),有以下三種方案:
- 重疊構造器模式
根據參數的不同組合,編寫多個簽名不同的構造器或靜態工廠方法。優點:線程安全,參數組合少時簡單;缺點:當參數量很大,且其組合很多時,代碼編寫麻煩且可讀性差 - JavaBean模式
調用一個構造器類創建對象并設置必要的參數,再通過setter方法類設置每個可選參數。優點:實例簡單,代碼簡潔,可擴展性強。缺點:構造過程被分為多個調用方法,難以保證一致性,(多線程)易出錯難調試;無法實例化一個不可變對象(致命) - Builder模式
不直接生成想要的對象,而是先用必要的參數實例化一個builder,再用setter方法設置每個可選參數,最后調用無參的build方法來生成不可變的對象。既能保證重疊構造器模式的安全性,也能保證像JavaBean一樣簡單可讀
第三條:用私有構造器或者枚舉類型強化Singleton屬性
實現Singleton有以下三種方法:
- 將構造器私有化,將實例設為公有的靜態final成員。私有構造器只被調用一次,用來實例化單例:
public class FirstSingleton {
public String message;
public static final FirstSingleton INSTANCE = new FirstSingleton();
private FirstSingleton(){
message = "Hello World! First Singleton!";
}
}
由于缺少公有的或者受保護的構造器,所以保證了單例的全局唯一性。
注意:享有特權的客戶端可以借助AccessibleObject.setAccessibe方法,通過反射機制調用私有構造器。如果需要抵御這種攻擊,可以修改私有構造器,使其在構造第二個實例時拋出異常。
- 公有的成員是個靜態工廠方法:
public class SecondSingleton {
public String message;
private static final SecondSingleton INSTANCE = new SecondSingleton();
private SecondSingleton(){
message = "Hello World! Second Singleton!";
}
public static SecondSingleton getInstance(){
return INSTANCE;
}
}
對于靜態方法getInstance的所有調用,都會返回同一個對象引用(上述注意依然使用)。
公有域方法的主要好處在于,組成類的成員的聲明很清楚第表明了這個類是一個Singleton:公有的靜態域是final的,所以該域將總是包含相同的對象引用。
對于以上兩種方法,在序列化時,僅僅在聲明中加上
implements Serializable
是不夠的。為了維護并保證Singleton,必須聲明所有的實例域都是瞬時的(transient)的,并提供一個readResove實例。否則,每次反序列化一個實例時,都會創建一個新的實例:
private Object readResolve(){
return INSTANCE;
}
- 編寫一個包含單個元素的枚舉類型:
public enum ThirdSingleton {
INSTANCE;
public String message = "Hello World! Third Singleton!";
}
這種方法在功能上與公有域方法相近,但是其更加簡潔,無償地提供了序列化機制,絕對防止多次實例化,即使是在面對復雜的序列化或者反射攻擊時。單元素的枚舉類型已經成為實現Singleton的最佳方法。
第四條:通過私有化構造器強化不可實例的能力
有時候,我們需要編寫只包含靜態方法和靜態域的類。這些工具類不希望被實例化,實例對它沒有任何意義。這個時候,我們需要私有化構造器,防止其在無意間被實例化。
第五條:避免創建不必要的對象
一般來說,最好能重用對象而不是每次需要 的時候就創建一個相同功能的新對象。
- 重用不可變對象:如果對象是不可變的,它就始終可以被重用(單例模式、String對象池)
- 重用已知不會被修改的可變對象。創建某些對象的成本是十分昂貴的,且其在創建之后,其值一般不會改變,此時我們可以考慮重用這個對象已減少創建成本。
- 使用適配器(適配器是指這樣一個對象:它把功能委托給一個后備對象,從而為后備對象提供一個可以替代的接口)。如Map類的KeySet方法,它返回一個Set對象。對于一個Map而言,它的key是可變的,但是keySet對象是唯一的,只是其內容會發生變化。
- 優先使用基本類型而不是自動裝箱基本類型,當心無意識的自動裝箱。
第六條:消除過期的對象引用
在支持垃圾回收的語言中,內存泄漏是很隱蔽的,稱之為“無意識的對象保持(unintentional object retention)“更為恰當。如果一個對象被無意識保留起來,那么垃圾回收機制不僅不會去處理這個對象,而且不會處理這個對象所引用的所有其他對象,久而久之會對性能造成潛在的重大影響。
這種問題一般出現在堆、棧、數組、鏈表等數據結果在pop對象時沒有消除引用,如
size--;
這種問題的解決方法很簡單,一旦對象過期,清空這些引用即可stack[size--] = null;
內存泄漏的常見來源:
- 類自己管理內存。當我們需要寫自己管理內存 的類時,如手動實現一個stack,應謹記一旦元素不需要用到,要立即釋放(消除引用),以便垃圾回收機制能及時回收。
- 緩存。緩存中的對象極易被遺忘。-> 使用WeakHashMap代表緩存
- 監聽器及其他回調。如果你實現了一個API,客戶端在這個API中注冊回調,卻沒有顯式取消注冊,那么除非你采取某些動作,否則它們就會被積聚。確保回調立即被當做立即回收的最佳方法是只保存它們的弱引用。
第七條:避免使用終結方法
終結方法(finalizer)通常是不可預測的,也是很危險的。
終結方法通常在垃圾回收機制回收對象時負責執行,其執行時間取決于jvm設計、配置以及程序執行過程。而且,java規范并不保證終結方法一定會被執行。還有一點,使用終結方法會帶來嚴重的性能損耗。
若一個對象需要在其終結時進行某些動作,可以考慮使用顯式終結動作,并要求所有客戶端在結束該實例時調用該方法,例如InputStream接口的close方法
終結方法的合法用途:
- 充當顯式終結方法的“安全網(safety net)。
- 終結非關鍵的本地資源(一般指本地對等體native peer),即本地對象。