2011/10/04

Java中的Singleton實作

在Java 1.5之前
以往我們可能用提供 private constructor 和 static final field 的方式,來確保只會得到唯一的 Class 實例
/* 
 * 獨一無二的貓王(Elvis),這世上就只有一個貓王 
 * Singleton with public final field 
 */
public class Elvis {
   public static final Elvis INSTANCE = new Elvis();
   private Elvis() { //忽略細節 }
   public void leaveTheBuilding() { //忽略細節 }
}
當需要取得貓王實例時,簡單的存取 Elvis.INSTANCE 即可。不過 private constructor 依然有一個問題,用 reflection 機制的 AccessibleObject.setAccessible 可以破解 private 的限制,如果要防範此一漏洞,還是得在 constructor 內做些手腳。


或是用 static factory 的方式,來提供唯一的 Class 實例
/* 
 * 獨一無二的貓王(Elvis),這世上就只有一個貓王 
 * 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() { //忽略細節 }
}
雖然有同上的問題(private constructor 部分),但是此作法相較起來更有彈性,如果你改變了想法,不想用 singleton 的方式實作此 Class,可以在不變動 API 的情況下修改 getInstance() 達成。


但是無論用上面哪種方式,當你需要一個 Serializable 的 Singleton 時,都會遇到一個同樣的問題:每次 deserialize 時都會建立一個新的實例。

解法就是將所有 field 宣告為 transient,並加上 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;
}
如果有定義此方法,則它會自動在 deserialize 時被呼叫,並回傳我們指定的物件。

Java 1.5之後的最好的實作方式
現在有了第三種簡明又實用的新解法 - 實作單一元素的 Enum。
// Enum singleton - the preferred approach
public enum Elvis {
   INSTANCE;
   public void leaveTheBuilding() { ... }
}
這種做法接近 public final field 而且
  • 程式碼更簡潔
  • 沒有 private constructor 漏洞
    (因為 Enum 型別像抽象類別一樣不能被實例化)
  • 本身還提供了 Serialization 機制
  • Enum 本身的特性確保只會有一個實體

所以單一元素的 Enum 是目前最好的 Singleton 實作方式。