Tips
《Effective Java, Third Edition》一書英文版已經出版,這本書的第二版想必不少人都讀過,號稱Java四大名著之一,不過第二版2009年出版,到如今已經將近8年的時間,但隨着Java 6,7,8,甚至9的發佈,Java語言發生了深入的變化。
在這裏第一時間翻譯成中文版。供你們學習分享之用。app
單例是一個僅實例化一次的類[Gamma95]。單例對象一般表示無狀態對象,如函數(條目 24)或一個本質上惟一的系統組件。讓一個類成爲單例會使測試它的客戶變得困難,由於除非實現一個做爲它類型的接口,不然不可能用一個模擬實現替代單例。函數
有兩種常見的方法來實現單例。二者都基於保持構造方法私有和導出公共靜態成員以提供對惟一實例的訪問。在第一種方法中,成員是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的實例就會存在——很少也很多。客戶端所作的任何事情都不能改變這一點,但須要注意的是:特權客戶端可使用AccessibleObject.setAccessible
方法,以反射方式調用私有構造方法(條目 65)。若是須要防護此攻擊,請修改構造方法,使其在請求建立第二個實例時拋出異常。測試
在第二個實現單例的方法中,公共成員是一個靜態的工廠方法:ui
// 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實例(與前面提到的警告相同)。線程
公共屬性方法的主要優勢是API明確表示該類是一個單例:公共靜態屬性是final的,因此它老是包含相同的對象引用。 第二個好處是它更簡單。翻譯
靜態工廠方法的一個優勢是,它能夠靈活地改變你的想法,不管該類是否爲單例而沒必要更改其API。 工廠方法返回惟一的實例,可是能夠修改,好比,返回調用它的每一個線程的單獨實例。 第二個好處是,若是你的應用程序須要它,能夠編寫一個泛型單例工廠(generic singleton factory )(條目30)。 使用靜態工廠的最後一個優勢是方法引用能夠用supplier
,例如Elvis :: instance
等同於Supplier<Elvis>
。 除非與這些優勢相關的,不然公共屬性方法是可取的。code
建立一個使用這兩種方法的單例類(第12章),僅僅將implements Serializable
添加到聲明中是不夠的。爲了維護單例的保證,聲明全部的實例屬性爲transient
,並提供一個readResolve
方法(條目89)。不然,每當序列化實例被反序列化時,就會建立一個新的實例,在咱們的例子中,致使出現新的Elvis實例。爲了防止這種狀況發生,將這個readResolve
方法添加到Elvis類:對象
// 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; }
實現一個單例的第三種方法是聲明單一元素的枚舉類:blog
// Enum singleton - the preferred approach public enum Elvis { INSTANCE; public void leaveTheBuilding() { ... } }
這種方式相似於公共屬性方法,但更簡潔,提供了免費的序列化機制,並提供了針對多個實例化的堅固保證,即便是在複雜的序列化或反射攻擊的狀況下。這種方法可能感受有點不天然,可是單一元素枚舉類一般是實現單例的最佳方式。注意,若是單例必須繼承Enum
之外的父類(儘管能夠聲明一個Enum
來實現接口),那麼就不能使用這種方法。