1. 程式人生 > >Java枚舉enum以及應用:枚舉實現單例模式

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

tee configure adr 自由 這樣的 pre 單例模式 做到 build

枚舉作為一個常規的語言概念,一直到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實現,那麽代碼如下:

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枚舉模式的話,誠然可以用一些類似++,——的語法糖,但是也要更多的考慮到安全性的問題。

如果僅此而已,那麽枚舉也沒什麽單獨拿出來寫博客的價值。

我個人對enum感興趣主要是因為之前在介紹Singleton時有一個非常經驗的枚舉實現的單例,代碼如下:

enum SingletonDemo{
    INSTANCE;
    public void otherMethods(){
        System.out.println("Something");
    }
}

簡簡單單的一點代碼就實現了一個線程安全,lazy loading的單例,與其說是寫法鬼斧神工,不如說是恰如其分地應用了enum的性質。

在用enum實現Singleton時我曾介紹過三個特性,自由序列化,線程安全,保證單例。這裏我們就要探討一下why的問題。

首先,我們都知道enum是由class實現的,換言之,enum可以實現很多class的內容,包括可以有member和member function,這也是我們可以用enum作為一個類來實現單例的基礎。另外,由於enum是通過繼承了Enum類實現的,enum結構不能夠作為子類繼承其他類,但是可以用來實現接口。此外,enum類也不能夠被繼承,在反編譯中,我們會發現該類是final的。

其次,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去實現反而麻煩且無必要。不過至少說明這樣做也許需要一個解決自由序列化的問題。

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