Java枚舉enum以及應用:枚舉實現單例模式

枚舉做爲一個常規的語言概念,一直到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去實現反而麻煩且無必要。不過至少說明這樣作也許須要一個解決自由序列化的問題。

相關文章
相關標籤/搜索