Singleton指僅僅被實例化一次的類 [Gamma95]。Singleton一般表明無狀態的對象,例如函數(第24項)或者本質上惟一的系統組件。使類稱爲Singleton會使它的客戶端測試變得十分困難,由於除非它實現了做爲其類型的接口,不然不可能將模擬實現替換爲單例。java
實現單例的方法有兩種。 二者都基於保持構造函數私有並導出公共靜態成員以提供對惟一實例的訪問。 在一種方法中,該成員是final字段:編程
// Singleton with public final field public class Elvis { public static final Elvis INSTANCE = new Elvis(); private Elvis() { ... } public void leaveTheBuilding() { ... } }
私有構造器只調用一次,用來初始化靜態變量Elvis.INSTANCE
。因爲缺乏public
或者protect
屬性的構造器,這就保證了Elvis
的全局一致性:一旦Evlis
類被實例化,只會存在一個Elvis
實例,很少也很多。客戶端所作的任何事情都沒法改變這一點,但有一點須要注意:享有特權的客戶端能夠藉助AccessibleObject.setAccessible
方法反射性地調用私有構造函數(第65項)。 若是你須要防護此攻擊,請修改構造函數以使其在要求建立第二個實例時拋出異常。微信
在實現Singleton的第二種方法中,公有的成員是個靜態工廠方法:app
// 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
實例(上述提醒依然適用)。less
公有域方法的主要好處在於,組成類的成員的聲明很清楚地聲明瞭這個類是一個Singleton:公有的靜態域是final的,因此該域老是包含同一個對象的引用。第二個好處就是它更加簡單。函數
工廠方法的優點之一在於,它提供了靈活性:在不改變其API的前提下,咱們能夠改變類是否應該爲Singleton的想法。工廠方法返回惟一實例,可是,它能夠很容易被修改,好比改爲每一個調用該方法的線程返回一個惟一的實例。第二個優勢是,若是你的應用須要,你能夠編寫泛型單例工廠(第30項)。使用靜態工廠的最後一個優勢是方法參考能夠用做供應商,例如Elvis::instance
是供應商<Elvis>。除非這些優點中的一個是相關的,不然公共領域方法更可取。(A final advantage of using a static factory is that a method reference can be used as a supplier, for example Elvis::instance is a Supplier<Elvis> .Unless one of these advantages is relevant, the public field approach is preferable.)測試
爲了使利用這其中一種方法實現的Singleton類編程可序列化的(第12章),僅僅在聲明中加上「implements Serializable」是不夠的。爲了維護並保證Singleton,必須聲明全部實例域都是瞬時(transient)的,並提供一個readResolve
方法(第89項)。不然,每次反序列化時,都會建立一個新的實例,在咱們的示例中,會致使「假冒的Elvis」。爲了防止這種狀況,要在Elvis類中加入下面這個readResolve方法:ui
// 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; }
實現Singleton還有第三種方法。只須要編寫一個包含單個元素的枚舉類型:spa
// Enum singleton - the preferred approach public enum Elvis { INSTANCE; public void leaveTheBuilding() { ... } }
這種方法相似於公共領域方法,但它更簡潔,免費提供序列化機制,並提供了對多個實例化的鐵定保證,即便面對複雜的序列化或反射攻擊。這種方法可能會有點不天然,但單元素枚舉類型一般是實現單例的最佳方法。請注意,若是你的單例必須擴展Enum
之外的超類,則不能使用此方法(儘管你能夠聲明枚舉來實現接口)。線程