1. 程式人生 > 實用技巧 >Item3 Enforce the singleton property with a private constructor or an enum type.

Item3 Enforce the singleton property with a private constructor or an enum type.

單例是一個只例項化一次的類,通常用來表示無狀態物件,但注意,將一個類設計為單例會使它的客戶端測試變得困難。
實現單例有兩種方法,通過建構函式私有化或通過列舉類

一、建構函式私有化:

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修飾,如果是則丟擲異常,反射失敗)。