Java 高手速成 | 单例模式实现方式——枚举
在 Java 语言中,如果综合考虑线程安全和延迟加载,IoDH(Initialization Demand Holder)无疑是一种比较好的实现方式,它巧妙利用了 Java 静态内部类的特点。但是,IoDH 的实现方式也存在一些问题。
那么,除了 IoDH 外,在 Java 语言中还有没有更好的单例模式实现方法呢?
答案是肯定的。
01、背景
首先来分析一下克隆、反射和反序列化对单例模式的破坏。
在其他创建型设计模式的学习中,我们已经了解,除了直接通过 new 和使用工厂来创建对象以外,还可以通过克隆、反射和反序列化等方式来创建对象。
但是用这些方式来创建对象时有可能会导致单例对象的不唯一,如何解决这些问题呢?
为了防止客户端使用克隆方法来创建对象,单例类不能实现 Cloneable 接口,即不能支持 clone()方法。
由于反射可以获取到类的构造函数,包括私有构造函数,因此反射可以生成新的对象。【如何解决:采用枚举实现】
采用一些传统的实现方法都不能避免客户端通过反射来创建新对象,此时,我们可以通过枚举单例对象的方式来解决该问题。
在原型模式中,我们可以通过反序列化实现深克隆,反序列化也会生成新的对象。具体来说就是每调用一次 readObject()方法,都将会返回一个新建的实例对象,这个新建的实例对象不同于类在初始化时创建的实例对象。
那么,如何防止反序列化创建对象呢?解决方法一是类不能实现 Serializable 接口,即不允许该类支持序列化,这将导致类的应用受限制(有时候我们还是需要对一个对象进行持久化处理);解决方法二就是本文将要详细介绍的枚举实现。
02、简单实现
下面我们分析如何使用枚举 Enum 来实现单例模式。
Google 首席 Java 架构师、《Effective Java》一书作者、Java 集合框架的开创者 Joshua Bloch 在 Effective Java 一书中提到:单元素的枚举类型已经成为实现 Singleton 的最佳方法。
在这种实现方式中,既可以避免多线程同步问题;还可以防止通过反射和反序列化来重新创建新的对象。在很多优秀的开源代码中,我们经常可以看到使用枚举方式来实现的单例模式类。
下面我们来详细分析如何使用枚举实现单例模式。
枚举是在 JDK1.5 以及以后版本中增加的一个“语法糖”,它主要用于维护一些实例对象固定的类。例如一年有四个季节,就可以将季节定义为一个枚举类型,然后在其中定义春、夏、秋、冬四个季节的枚举类型的实例对象。 按照 Java 语言的命名规范,通常,枚举的实例对象全部采用大写字母定义,这一点与 Java 里面的常量是相同的。
首先我们来看一下最简单的单例模式枚举实现。
因为 Java 虚拟机会保证枚举对象的唯一性,因此每一个枚举类型和定义的枚举变量在 JVM 中都是唯一的。
最简单的实现方式如下代码所示:
大家可以看到,我们定义了一个枚举类型 Singleton,在其中定义了一个枚举变量 instance,同时还提供了业务方法 businessMethod。
接下来我们看一下客户端代码,如下所示:
在 main 函数中,我们通过 Singleton.instance 获得两个对象 s1 和 s2,然后比较 s1 是否等于 s2,最后输出 true,说明 s1 和 s2 是同一个对象,所得到的对象具有唯一性。
03、经典实现
如果需要将一个已有的类改造为单例类,也可以使用枚举的方式来实现。
下面我们来看一下对应的实现代码。
在代码中,我们首先将 Singleton 类的构造函数设置为 private 私有的,然后在 Singleton 类中定义一个静态的枚举类型 SingletonEnum。
在 SingletonEnum 中定义了枚举类型的实例对象 Singleton,再按照单例模式的要求在其中定义一个 Singleton 类型的对象 instance,其初始值为 null;我们需要将 SingletonEnum 的构造函数改为私有的,在私有构造函数中创建一个 Singleton 的实例对象;最后在 getInstance()方法中返回该对象。
在实现过程中,Java 虚拟机会保证枚举类型不能被反射并且构造函数只被执行一次。
正如我们前面所讲的 Singleton 类本身就是一个普通类,它里面还包含了其他业务方法。在这里我们只需要在其中增加一个内部枚举类型来存储和创建它的唯一实例即可,这和前面的静态内部类的实现有点相似,但是枚举实现可以很好地解决反射和反序列化会破坏单例模式的问题,提供了一种更加安全和可靠的单例模式实现机制。
我们在客户端代码中进行一下测试:
大家可以看到,在这里我们通过调用枚举 SingletonEnum 中定义的枚举实例对象 SINGLETON 的 getInstance()方法获取对象 s2 和 s2,然后比较 s1 是否等于 s2,最后输出 true,输出结果说明 s1 和 s2 是同一个对象,所得到的对象具有唯一性。
04、结语
由于单例模式的枚举实现代码比较简单,而且又可以利用枚举的特性来解决线程安全和单一实例的问题,还可以防止反射和反序列化对单例的破坏,因此在很多书和文章中都强烈推荐将该方法作为单例模式的最佳实现方法。
版权声明: 本文为 InfoQ 作者【TiAmo】的原创文章。
原文链接:【http://xie.infoq.cn/article/ab21f405cb13456f5f271701b】。文章转载请联系作者。
评论