枚舉做爲一個常規的語言概念,一直到Java5才誕生不得不說有點奇怪,以致於到如今爲止不少程序員仍然更喜歡用static final的形式去命名常量而不使用,通常狀況下,Java程序員用這種方式去實現枚舉:程序員
class EnumByClass{ public static final int RED=0; public static final int GREEN=1; public static final int BLUE=2; }
這種方式實現的枚舉也叫int枚舉模式,儘管很經常使用,可是由int實現的枚舉很難保證安全性,即當調用不在枚舉範圍內的數值時,須要額外的維護。另外 ,也不利於查看log和測試。安全
此時,咱們須要開始使用Java的枚舉類型,例如上面的int枚舉模式類若是用enum實現,那麼代碼以下:session
enum Color{ RED,GREEN,BLUE; }
上述是將枚舉做爲常量集合的簡單用法,實際上,枚舉更多的仍是用於switch,也是在這時才能發現枚舉相對於int枚舉模式的好處,這裏面舉一個用enum實現switch的例子:測試
enum Color{ RED,GREEN,BLUE; } public class Hello { public static void main(String[] args){ Color color=Color.RED; int counter=10; while (counter-->0){ switch (color){ case RED: System.out.println("Red"); color=Color.BLUE; break; case BLUE: System.out.println("Blue"); color=Color.GREEN; break; case GREEN: System.out.println("Green"); color=Color.RED; break; } } } }
若是咱們用int枚舉模式的話,誠然能夠用一些相似++,——的語法糖,可是也要更多的考慮到安全性的問題。ui
若是僅此而已,那麼枚舉也沒什麼單獨拿出來寫博客的價值。this
我我的對enum感興趣主要是由於以前在介紹Singleton時有一個很是經驗的枚舉實現的單例,代碼以下:spa
enum SingletonDemo{ INSTANCE; public void otherMethods(){ System.out.println("Something"); } }
簡簡單單的一點代碼就實現了一個線程安全的單例,與其說是寫法鬼斧神工,不如說是恰如其分地應用了enum的性質。線程
在用enum實現Singleton時我曾介紹過三個特性,自由序列化,線程安全,保證單例。這裏咱們就要探討一下why的問題。設計
首先,咱們都知道enum是由class實現的,換言之,enum能夠實現不少class的內容,包括能夠有member和member function,這也是咱們能夠用enum做爲一個類來實現單例的基礎。另外,因爲enum是經過繼承了Enum類實現的,enum結構不可以做爲子類繼承其餘類,可是能夠用來實現接口。此外,enum類也不可以被繼承,在反編譯中,咱們會發現該類是final的。code
其次,enum有且僅有private的構造器,防止外部的額外構造,這剛好和單例模式吻合,也爲保證單例性作了一個鋪墊。這裏展開說下這個private構造器,若是咱們不去手寫構造器,則會有一個默認的空參構造器,咱們也能夠經過給枚舉變量參量來實現類的初始化。這裏舉一個例子。
enum Color{ RED(1),GREEN(2),BLUE(3); private int code; Color(int code){ this.code=code; } public int getCode(){ return code; } }
須要注意的是,private修飾符對於構造器是能夠省略的,但這不表明構造器的權限是默認權限。
目前咱們對enum的結構和特性有了初步的瞭解,接下來探究一下原理層次的特性。
想要了解enum是如何工做的,就要對其進行反編譯。
反編譯後就會發現,使用枚舉其實和使用靜態類內部加載方法原理相似。枚舉會被編譯成以下形式:
public final class T extends Enum{
...
}
其中,Enum是Java提供給編譯器的一個用於繼承的類。枚舉量的實現實際上是public static final T 類型的未初始化變量,以後,會在靜態代碼中對枚舉量進行初始化。因此,若是用枚舉去實現一個單例,這樣的加載時間其實有點相似於餓漢模式,並無起到lazy-loading的做用。
對於序列化和反序列化,由於每個枚舉類型和枚舉變量在JVM中都是惟一的,即Java在序列化和反序列化枚舉時作了特殊的規定,枚舉的writeObject、readObject、readObjectNoData、writeReplace和readResolve等方法是被編譯器禁用的,所以也不存在實現序列化接口後調用readObject會破壞單例的問題。
對於線程安全方面,相似於普通的餓漢模式,經過在第一次調用時的靜態初始化建立的對象是線程安全的。
所以,選擇枚舉做爲Singleton的實現方式,相對於其餘方式尤爲是相似的餓漢模式主要有如下優勢:
1. 代碼簡單
2. 自由序列化
至於lazy-loading,考慮到通常狀況不存在調用單例類又不須要實例化單例的狀況,因此即使不能作到很好的lazy-loading,也並非大問題。換言之,除了枚舉這種方案,餓漢模式也在單例設計中普遍的被應用。例如,Hibernate默認的單例,獲取sessionFactory用的HibernateUtil類創建方式以下:
public class HibernateUtil { private static final SessionFactory ourSessionFactory; static { try { Configuration configuration = new Configuration(); configuration.configure(); ourSessionFactory = configuration.buildSessionFactory(); } catch (Throwable ex) { throw new ExceptionInInitializerError(ex); } } public static Session getSession() throws HibernateException { return ourSessionFactory.openSession(); } }
這是一個典型的餓漢模式,考慮到這個單例只有一個方法即getSession,顯然這種模式自己就是最優的且簡潔的。這裏面因爲SessionFactory的建立並非用系統默認的方式,若是想要用enum去實現反而麻煩且無必要。不過至少說明這樣作也許須要一個解決自由序列化的問題。