Java高手速成 | 单例模式实现方式——枚举

举报
TiAmoZhang 发表于 2023/02/14 15:13:10 2023/02/14
【摘要】 在Java语言中,如果综合考虑线程安全和延迟加载,IoDH(Initialization Demand Holder)无疑是一种比较好的实现方式,它巧妙利用了Java静态内部类的特点。但是,IoDH的实现方式也存在一些问题。 那么,除了IoDH外,在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中都是唯一的。

最简单的实现方式如下代码所示:

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是同一个对象,所得到的对象具有唯一性。

03、经典实现


如果需要将一个已有的类改造为单例类,也可以使用枚举的方式来实现。

下面我们来看一下对应的实现代码。

public class Singleton {
    private Singleton(){
    }
    public static enum SingletonEnum {
        SINGLETON;
        private Singleton instance = null;
        private SingletonEnum(){
            instance = new Singleton();
        }
        public Singleton getInstance(){
            return instance;
        }
    }
}

在代码中,我们首先将Singleton类的构造函数设置为private私有的,然后在Singleton类中定义一个静态的枚举类型SingletonEnum。

在SingletonEnum中定义了枚举类型的实例对象Singleton,再按照单例模式的要求在其中定义一个Singleton类型的对象instance,其初始值为null;我们需要将SingletonEnum的构造函数改为私有的,在私有构造函数中创建一个Singleton的实例对象;最后在getInstance()方法中返回该对象。

在实现过程中,Java虚拟机会保证枚举类型不能被反射并且构造函数只被执行一次。

正如我们前面所讲的Singleton类本身就是一个普通类,它里面还包含了其他业务方法。在这里我们只需要在其中增加一个内部枚举类型来存储和创建它的唯一实例即可,这和前面的静态内部类的实现有点相似,但是枚举实现可以很好地解决反射和反序列化会破坏单例模式的问题,提供了一种更加安全和可靠的单例模式实现机制。

我们在客户端代码中进行一下测试:

……
    public static void main(String args[]) {
        Singleton s1 = SingletonEnum.SINGLETON.getInstance();
        Singleton s2 = SingletonEnum.SINGLETON.getInstance();
        System.out.println(s1==s2);
    }
    ……

大家可以看到,在这里我们通过调用枚举SingletonEnum中定义的枚举实例对象SINGLETON的getInstance()方法获取对象s2和s2,然后比较s1是否等于s2,最后输出true,输出结果说明s1和s2是同一个对象,所得到的对象具有唯一性。

04、结语


由于单例模式的枚举实现代码比较简单,而且又可以利用枚举的特性来解决线程安全和单一实例的问题,还可以防止反射和反序列化对单例的破坏,因此在很多书和文章中都强烈推荐将该方法作为单例模式的最佳实现方法。

【版权声明】本文为华为云社区用户原创内容,未经允许不得转载,如需转载请自行联系原作者进行授权。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。