Item3 Enforce the singleton property with a private constructor or an enum type.
阿新 • • 發佈:2020-12-17
單例是一個只例項化一次的類,通常用來表示無狀態物件,但注意,將一個類設計為單例會使它的客戶端測試變得困難。
實現單例有兩種方法,通過建構函式私有化或通過列舉類。
一、建構函式私有化:
1.通過建構函式建立例項
public class Elvis {
public static final Elvis INSTANCE = new Elvis();
private Elvis() { ... }
public void leaveTheBuilding() { ... }
}
將欄位設為final,也就是說該類只會使用一次建構函式,一旦初始化了Elvis類,就只會存在一個例項。
2.通過靜態工廠方法建立例項
方法同1,但在某些場景下需要實現Serializable介面,這時應宣告欄位為transient。並且提供readSolve方法,這是為了在反序列化情況下返回相同的例項INSTANCE,否則jvm在反序列化過程中會預設呼叫建構函式new一個例項,這顯然不符合單例模式,但若目標類有readResolve方法,jvm會通過反射的方式呼叫要被反序列化的類中的readResolve方法。
public class Elvis implements Serializable { private static final long serialVersionUID = -4658677464433792256L; // make sure the transient private final transient String[] favoriteSongs = { "Hound Dog", "Heartbreak Hotel" }; private static final Elvis INSTANCE = new Elvis(); private Elvis() {} public static Elvis getInstance() { return INSTANCE; } /** * if you want to implemnt Serializable, * you should make this method, to avoid the jvm return a new instance * when deserializing */ private Object readResolve() { return INSTANCE; } public void printSongs() { System.out.println(Arrays.toString(favoriteSongs)); } }
注意,以上方法都可以通過反射方法類破壞單例,如需防範反射攻擊,請修改建構函式(建構函式呼叫計數/原子操作),此處不展開說明。
二、列舉型別
列舉型別顯然更加簡潔,避免了序列化、反射問題,通常是實現單例的最佳方法。
public enum MySingleton { INSTANCE; private final String[] favoriteSongs = { "Hound Dog", "Heartbreak Hotel" }; public void printFavorites() { System.out.println(Arrays.toString(favoriteSongs)); } }
列舉類預設實現了序列化,並且在反序列化中返回的是同一個例項,其次,列舉可以在很大程度上避免反射攻擊(反射在通過newInstance建立物件時,會檢查該類是否是ENUM修飾,如果是則丟擲異常,反射失敗)。