枚举实现单例模式的优点:
1.线程安全
2.自有序列化
3.保证单例
首先,我们都知道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去实现反而麻烦且无必要。不过至少说明这样做也许需要一个解决自由序列化的问题。
class Resource{
}
public enum SomeThing {
INSTANCE;
private Resource instance;
SomeThing() {
instance = new Resource();
}
public Resource getInstance() {
return instance;
}
}
上面的类Resource是我们要应用单例模式的资源,具体可以表现为网络连接,数据库连接,线程池等等。
获取资源的方式很简单,只要 SomeThing.INSTANCE.getInstance() 即可获得所要实例。下面我们来看看单例是如何被保证的:
首先,在枚举中我们明确了构造方法限制为私有,在我们访问枚举实例时会执行构造方法,同时每个枚举实例都是static final类型的,也就表明只能被实例化一次。在调用构造方法时,我们的单例被实例化。
也就是说,因为enum中的实例被保证只会被实例化一次,所以我们的INSTANCE也被保证实例化一次。
可以看到,枚举实现单例还是比较简单的,除此之外我们再来看一下Enum这个类的声明:
public abstract class Enum<E extends Enum<E>>
implements Comparable<E>, Serializable
可以看到,枚举也提供了序列化机制。某些情况,比如我们要通过网络传输一个数据库连接的句柄,会提供很多帮助。
最后借用 《Effective Java》一书中的话,
单元素的枚举类型已经成为实现Singleton的最佳方法。
-----------------