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去实现反而麻烦且无必要。不过至少说明这样作也许须要一个解决自由序列化的问题。

相关文章
相关标签/搜索