单例模式之枚举实现

通过枚举实现单例模式,可确保对象唯一性,同时解决线程安全、反射及反序列化等问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

        如果你没有学过单例模式,请点击:确保对象的唯一性——单例模式

        有很多网友留言说我漏掉了一种非常重要的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 BlochEffective 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_软件工程,软件教育,设计模式-优快云博客

### 枚举类型实现单例模式Java 中,枚举类型(`enum`)可以通过其语言特性来实现单例模式。这种实现方式不仅简而且安全,能够防止反序列化攻击以及反射机制破坏的完整性。 #### 定义枚举 以下是一个典型的枚举单例模式的定义: ```java public enum SingletonEnum { INSTANCE; public void doSomething() { System.out.println("执行某些操作..."); } } ``` 上述代码中,`SingletonEnum` 是一个枚举类型,其中 `INSTANCE` 是它的唯一实。由于枚举类型的特殊性,在 JVM 加载时会自动创建并初始化此实,并且无法通过其他方式再创建新的实[^1]。 #### 调用方法示 下面展示了如何使用枚举单例模式中的实及其方法: ```java package java_mode_06; public class Main { public static void main(String[] args) { // 获取 SingletonEnum singleton = SingletonEnum.INSTANCE; SingletonEnum singleton1 = SingletonEnum.INSTANCE; // 执行方法 singleton.doSomething(); // 验证是否为同一个实 System.out.println("1:" + singleton); System.out.println("2:" + singleton1); // 对比非对象的行为 Test test = new Test(); Test test1 = new Test(); System.out.println("非1:" + test); System.out.println("非2:" + test1); } } class Test {} ``` 运行以上程序后,输出的结果如下所示: ``` 执行某些操作... 1:SingletonEnum.INSTANCE 2:SingletonEnum.INSTANCE 非1:Test@<hashcode> 非2:Test@<another_hashcode> ``` 可以看到,无论多少次访问 `SingletonEnum.INSTANCE`,得到的始终是同一个实;而普通的类则每次都会生成不同的新实[^2]。 #### 序列化的安全性 当涉及到对象的序列化与反序列化时,传统的单例模式可能会被破坏,因为反序列化过程通常会产生一个新的实。然而,对于枚举类型的来说,即使经过反序列化处理,仍然只会返回原始的那个一实。这是因为 JDK 的内部机制已经针对这种情况进行了优化——即任何尝试重新构建枚举的操作都将失败,转而返回已存在的那个枚举成员[^3]。 如果需要进一步增强对序列化场景的支持,《Effective Java》一书中提到可以在必要情况下覆盖 `readResolve()` 方法以确保实控制的有效性。 --- ###
评论 42
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值