如果你沒有學過單例模式,請點擊:確保物件的唯一性——單例模式,
有很多網友留言說我漏掉了一種非常重要的Java語言的單例模式實作方式——列舉,^_^
這篇姍姍來遲的博文將彌補這個“巨大的”缺陷,^_^~~~~~~~~~~~
在Java語言中,如果綜合考慮執行緒安全和延遲加載,IoDH(Initialization Demand Holder)無疑是一種比較好的實作方式【參見:確保物件的唯一性——單例模式 (四)】,它巧妙利用了Java靜態內部類的特點,
但是,但是,但是……IoDH的實作方式也存在一些問題,什么問題?我下面會給大家進行分析,
那么,除了IoDH外,在Java語言中還有沒有更好的單例模式實作方法呢?
答案是肯定的,
1. 背景
首先來分析一下克隆、反射和反序列化對單例模式的破壞,
在其他創建型設計模式的學習中,我們已經了解,除了直接通過new和使用工廠來創建物件以外,還可以通過克隆、反射和反序列化等方式來創建物件,
但是用這些方式來創建物件時有可能會導致單例物件的不唯一,如何解決這些問題呢?
(1) 為了防止客戶端使用克隆方法來創建物件,單例類不能實作Cloneable介面,即不能支持clone()方法,
(2) 由于反射可以獲取到類的建構式,包括私有建構式,因此反射可以生成新的物件,【如何解決:采用列舉實作】
采用一些傳統的實作方法都不能避免客戶端通過反射來創建新物件,此時,我們可以通過列舉單例物件的方式來解決該問題,
(3) 在原型模式中,我們可以通過反序列化實作深克隆,反序列化也會生成新的物件,具體來說就是每呼叫一次readObject()方法,都將會回傳一個新建的實體物件,這個新建的實體物件不同于類在初始化時創建的實體物件,
那么,如何防止反序列化創建物件呢?解決方法一是類不能實作Serializable介面,即不允許該類支持序列化,這將導致類的應用受限制(有時候我們還是需要對一個物件進行持久化處理);解決方法二就是本文將要詳細介紹的列舉實作,
2. 簡單實作
下面我們分析如何使用列舉Enum來實作單例模式,
Google 首席 Java 架構師、《Effective Java》一書作者、Java集合框架的開創者Joshua Bloch在Effective Java一書中提到:
單元素的列舉型別已經成為實作Singleton的最佳方法,【大佬真是這么說的】

在這種實作方式中,既可以避免多執行緒同步問題;還可以防止通過反射和反序列化來重新創建新的物件,在很多優秀的開源代碼中,我們經常可以看到使用列舉方式來實作的單例模式類,
下面我們來詳細分析如何使用列舉實作單例模式,
列舉是在JDK1.5以及以后版本中增加的一個“語法糖”,它主要用于維護一些實體物件固定的類,例如一年有四個季節,就可以將季節定義為一個列舉型別,然后在其中定義春、夏、秋、冬四個季節的列舉型別的實體物件, 按照Java語言的命名規范,通常,列舉的實體物件全部采用大寫字母定義,這一點與Java里面的常量是相同的,
首先我們來看一下最簡單的單例模式列舉實作,
因為Java虛擬機會保證列舉物件的唯一性,因此每一個列舉型別和定義的列舉變數在JVM中都是唯一的,
最簡單的實作方式如下代碼所示:
public enum Singleton {
INSTANCE;
public void businessMethod() {
System.out.println("我是一個單例!");
}
}
大家可以看到,我們定義了一個列舉型別Singleton,在其中定義了一個列舉變數instance,同時還提供了業務方法businessMethod,
接下來我們看一下客戶端代碼,如下所示:
public class MainClass {
public static void main(String[] args) {
Singleton s1 = Singleton.INSTANCE;
Singleton s2 = Singleton.INSTANCE;
System.out.println(s1==s2);
}
}
在main函式中,我們通過Singleton.instance獲得兩個物件s1和s2,然后比較s1是否等于s2,最后輸出true,說明s1和s2是同一個物件,所得到的物件具有唯一性,
3. 經典實作
如果需要將一個已有的類改造為單例類,也可以使用列舉的方式來實作,
下面我們來看一下對應的實作代碼,
public class Singleton {
private Singleton(){
}
public static enum SingletonEnum {
SINGLETON;
private Singleton instance = null;
private SingletonEnum(){
instance = new Singleton();
}
public Singleton getInstance(){
return instance;
}
}
}
在代碼中,我們首先將Singleton類的建構式設定為private私有的,然后在Singleton類中定義一個靜態的列舉型別SingletonEnum,
在SingletonEnum中定義了列舉型別的實體物件Singleton,再按照單例模式的要求在其中定義一個Singleton型別的物件instance,其初始值為null;我們需要將SingletonEnum的建構式改為私有的,在私有建構式中創建一個Singleton的實體物件;最后在getInstance()方法中回傳該物件,
在實作程序中,Java虛擬機會保證列舉型別不能被反射并且建構式只被執行一次,
正如我們前面所講的Singleton類本身就是一個普通類,它里面還包含了其他業務方法,在這里我們只需要在其中增加一個內部列舉型別來存盤和創建它的唯一實體即可,這和前面的靜態內部類的實作有點相似,但是列舉實作可以很好地解決反射和反序列化會破壞單例模式的問題,提供了一種更加安全和可靠的單例模式實作機制,
我們在客戶端代碼中進行一下測驗:
……
public static void main(String args[]) {
Singleton s1 = SingletonEnum.SINGLETON.getInstance();
Singleton s2 = SingletonEnum.SINGLETON.getInstance();
System.out.println(s1==s2);
}
……
大家可以看到,在這里我們通過呼叫列舉SingletonEnum中定義的列舉實體物件SINGLETON的getInstance()方法獲取物件s2和s2,然后比較s1是否等于s2,最后輸出true,輸出結果說明s1和s2是同一個物件,所得到的物件具有唯一性,
4. 結語
由于單例模式的列舉實作代碼比較簡單,而且又可以利用列舉的特性來解決執行緒安全和單一實體的問題,還可以防止反射和反序列化對單例的破壞,因此在很多書和文章中都強烈推薦將該方法作為單例模式的最佳實作方法,

OK,關于如何使用Java語言的列舉機制實作單例模式就介紹完了,你看懂了嗎?希望能夠有所識訓,
【作者:劉偉 http://blog.csdn.net/lovelion】
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/233538.html
標籤:其他
