1. 程式人生 > >EffectiveJava 第3條 :用私有構造器或者列舉型別強化Singleton屬性

EffectiveJava 第3條 :用私有構造器或者列舉型別強化Singleton屬性

Singleton只不過是指僅僅例項化一次的類[Gamma95, p. 127]。Singleton通常被用來代表那些本質上唯一的系統元件,比如視窗管理器或者檔案系統。使類成為Singleton會使它的客戶端測試變得十分困難,因為無法給Singleton替換模擬實現,除非它實現一個充當其型別的介面。

在Java 1.5發行版本之前,實現Singleton有兩種方法。這兩種方法都要把構造器保持為私有的,並匯出公有的靜態成員,以便允許客戶端能夠訪問該類的唯一例項。在第一種方法中,公有靜態成員是個final域:

// Singleton with public final field
public class Elvis {
public static final Elvis INSTANCE = new Elvis();
private Elvis() { ... }
public void leaveTheBuilding() { ... }
}

私有構造器僅被呼叫一次,用來初始化公有的靜態final域Elvis.INSTANCE。沒有公有的或者受保護的構造器,確保了Elvis的全域性唯一性:一旦初始化了Elvis類,將只存在一個Elvis例項,不多也不少。客戶端的任何行為都不會改變這一點,但要提醒一點:享有特權的客戶端可以藉助於AccessibleObject.setAccessible方法,通過反射機制(見第53條)呼叫私有構造器。如果需要抵禦這種攻擊,可以修改構造器,讓它在被要求建立第二個例項的時候丟擲異常。

在實現Singleton的第二種方法中,公有的成員是個靜態工廠方法:

// Singleton with static factory
public class Elvis {
private static final Elvis INSTANCE = new Elvis();
private Elvis() { ... }
public static Elvis getInstance() { return INSTANCE; }
public void leaveTheBuilding() { ... }
}

對於靜態方法Elvis.getInstance的所有呼叫,都會返回同一個物件引用,所以,永遠不會建立其他的Elvis例項(上述提醒依然適用)。

公有域方法的主要好處在於,組成類的成員的宣告很清楚地表明瞭這個類是一個Singleton:公有的靜態域是final的,所以該域將總是包含相同的物件引用。公有域方法在效能上不再有任何優勢:現代的JVM(Java Virtual Machine)實現幾乎都能夠將靜態工廠方法的呼叫內聯化(inline)。

工廠方法的主要優勢在於,它提供了靈活性:在不改變其API的前提下,我們可以改變該類是否應該為Singleton的想法。工廠方法返回該類的唯一例項,但是,它可以很容易被修改,比如改成為每個呼叫該方法的執行緒返回一個唯一的例項。第二個優勢與泛型(見第27條)有關。這些優勢之間通常都不相關,final域的方法比較簡單。

為了使利用這其中一種方法實現的Singleton類變成是可序列化的(Serializable)(見第11章),僅僅在宣告中加上"implements Serializable"是不夠的。為了維護並保證Singleton,必須宣告所有例項域都是瞬時(transient)的,並提供一個readResolve方法(見第77條)。否則,每次反序列化一個序列化的例項時,都會建立一個新的例項,比如說,在我們的例子中,會導致"假冒的Elvis"。為了防止這種情況,要在Elvis類中加入下面這個readResolve方法:

// readResolve method to preserve singleton property
private Object readResolve() {
// Return the one true Elvis and let the garbage collector
// take care of the Elvis impersonator.
return INSTANCE;
}

從Java 1.5發行版本起,實現Singleton還有第三種方法。只要編寫一個包含單個元素的列舉型別:
// Enum singleton - the preferred approach
public enum Elvis {
INSTANCE;
public void leaveTheBuilding() { ... }
}

這種方法在功能上與公有域方法相近,但是它更加簡潔,無償地提供了序列化機制,絕對防止多次例項化,即使是在面對複雜的序列化或者反射攻擊的時候。雖然這種方法還沒有被廣泛採用,但是單元素的列舉型別已經成為實現Singleton的最佳方法。