单例模式之枚举实现
如果你没有学过单例模式,请点击:确保对象的唯一性——单例模式。
有很多网友留言说我漏掉了一种非常重要的Java语言的单例模式实现方式——枚举。^_^
这篇姗姗来迟的博文将弥补这个“巨大的”缺陷。^_^~~~~~~~~~~~
在Java语言中,如果综合考虑线程安全和延迟加载,IoDH(Initialization Demand Holder)无疑是一种比较好的实现方式【参见:确保对象的唯一性——单例模式 (四)】,它巧妙利用了Java静态内部类的特点。
但是,但是,但是……IoDH的实现方式也存在一些问题。什么问题?我下面会给大家进行分析。
那么,除了IoDH外,在Java语言中还有没有更好的单例模式实现方法呢?
答案是肯定的。
1. 背景
首先来分析一下克隆、反射和反序列化对单例模式的破坏。
在其他创建型设计模式的学习中,我们已经了解,除了直接通过new和使用工厂来创建对象以外,还可以通过克隆、反射和反序列化等方式来创建对象。
但是用这些方式来创建对象时有可能会导致单例对象的不唯一,如何解决这些问题呢?
(1) 为了防止客户端使用克隆方法来创建对象,单例类不能实现Cloneable接口,即不能支持clone()方法。
(2) 由于反射可以获取到类的构造函数,包括私有构造函数,因此反射可以生成新的对象。【如何解决:采用枚举实现】
采用一些传统的实现方法都不能避免客户端通过反射来创建新对象,此时,我们可以通过枚举单例对象的方式来解决该问题。
(3) 在原型模式中,我们可以通过反序列化实现深克隆,反序列化也会生成新的对象。具体来说就是每调用一次readObject()方法,都将会返回一个新建的实例对象,这个新建的实例对象不同于类在初始化时创建的实例对象。
那么,如何防止反序列化创建对象呢?解决方法一是类不能实现Serializable接口,即不允许该类支持序列化,这将导致类的应用受限制(有时候我们还是需要对一个对象进行持久化处理);解决方法二就是本文将要详细介绍的枚举实现。
2. 简单实现
下面我们分析如何使用枚举Enum来实现单例模式。
Google 首席 Java 架构师、《Effective Java》一书作者、Java集合框架的开创者Joshua Bloch在Effective Java一书中提到:
单元素的枚举类型已经成为实现Singleton的最佳方法。【大佬真是这么说的】
在这种实现方式中,既可以避免多线程同步问题;还可以防止通过反射和反序列化来重新创建新的对象。在很多优秀的开源代码中,我们经常可以看到使用枚举方式来实现的单例类。
下面我们来详细分析如何使用枚举实现单例模式。
枚举是在JDK1.5以及以后版本中增加的一个“语法糖”,它主要用于维护一些实例对象固定的类。例如一年有四个季节,就可以将季节定义为一个枚举类型,然后在其中定义春、夏、秋、冬四个季节的枚举类型的实例对象。 按照Java语言的命名规范,通常,枚举的实例对象全部采用大写字母定义,这一点与Java里面的常量是相同的。
首先我们来看一下最简单的单例模式枚举实现。
因为Java虚拟机会保证枚举对象的唯一性,因此每一个枚举类型和定义的枚举变量在JVM中都是唯一的。
最简单的实现方式如下代码所示:
public enum Singleton {
INSTANCE;
public void businessMethod() {
System.out.println("我是一个单例!");
}
}
大家可以看到,我们定义了一个枚举类型Singleton,在其中定义了一个枚举变量INSTANCE,同时还提供了业务方法businessMethod()。
接下来我们看一下客户端代码,如下所示:
public class MainClass {
public static void main(String[] args) {
Singleton s1 = Singleton.INSTANCE;
Singleton s2 = Singleton.INSTANCE;
System.out.println(s1==s2);
}
}
在main()函数中,我们通过Singleton.INSTANCE获得两个对象s1和s2,然后比较s1是否等于s2,最后输出true,说明s1和s2是同一个对象,所得到的对象具有唯一性。
3. 结语
由于单例模式的枚举实现代码比较简单,而且又可以利用枚举的特性来解决线程安全和单一实例的问题,还可以防止反射和反序列化对单例的破坏,因此在很多书和文章中都强烈推荐将该方法作为单例模式的最佳实现方法。
OK,关于如何使用Java语言的枚举机制实现单例模式就介绍完了,你看懂了吗?希望能够有所收获。
【作者:刘伟 LoveLion_软件工程,软件教育,设计模式-CSDN博客】
更多推荐
所有评论(0)