Java 枚举单例

10 Sept 2024 | 4 分钟阅读

在本文中,我们将了解 Java 中的枚举单例。您将能够理解如何使用它、它的用途以及最重要的是它的优缺点。

让我们来理解一下编程中的单例是什么。

单例

一个类被称为单例,是指它在 JVM 中只有一个实例。多个线程会重用同一个单例类的对象。由于系统配置和窗口管理员需要为 JVM 下的所有线程和对象共享,因此单例最常用于表示它们。

可以使用多种常用技术来创建单例。

  • 使用公共静态最终字段
  • 使用公共静态工厂方法
  • 使用延迟初始化

通过使用私有构造函数,上述每种方法都强制执行不可实例化(无法创建实例)。即使在这种情况下,私有构造函数中没有需要执行的操作,我们也无法避免创建一个。因为如果我们这样做,默认构造函数(不带参数)将被隐式生成并赋予与类相同的访问修饰符。例如,如果类定义为 public,则默认构造函数为 public;同样,如果类被标记为 protected,则默认构造函数为 protected。

枚举单例

上面提到的三行代码创建了一个没有所有已提及问题的单例。由于枚举天然可序列化,我们无需使用可序列化接口来构造它。此外,不存在反射问题。因此,可以绝对确定 JVM 中只有一个单例实例。因此,在 Java 中创建单例时,建议使用此技术。

使用方式

以上是实现枚举单例的方法或函数体。接下来是主类。

需要注意的一点是,在序列化枚举时,字段变量实际上不会被序列化。例如,如果 SingletonEnum 类被序列化和反序列化,int value 字段的值将会丢失。

让我们了解 Java 枚举单例的优缺点

优点

编写枚举单例很简单

如果你在 Java 5 之前一直在构建单例,那么最大的优点就是意识到尽管有双重检查锁定,但仍然可能出现多个实例。尽管 Java 5 和 volatile 变量的保证已解决了这个问题,但许多初学者仍然觉得编写起来很困难。

与带有同步的双重检查锁定相比,枚举单例非常简单。如果您不同意,那么传统的带有双重检查锁定以及枚举单例的单例是可比的。

Java 的枚举单例实现默认是线程安全的,但任何额外的枚举方法都必须由程序员编写。

枚举单例自行处理了序列化

传统的单例还有一个问题,那就是如果你采用可序列化接口,它们将不再是单例,因为 readObject() 方法会自动返回一个新实例,就像 Java 构造函数一样。你可以通过使用 Singleton 替代 readResolve() 并丢弃新创建的实例来规避它,如下面的代码所示

如果你的 Singleton 类包含状态,这可能会变得更加复杂,你需要使它们可临时序列化,但 JVM 确保了枚举单例的序列化。

枚举实例的线程安全创建

枚举对象默认是线程安全的,所以你不必担心双重检查锁定。

总而言之,凭借保证的序列化和线程安全性,以及仅需几行代码的枚举,枚举单例方法是在 Java 5 世界中创建单例的理想方法。

文件名:Enum.java

输出

0
2

缺点

编码限制

枚举类中似乎有一些普通类不允许的东西。例如,使用构造函数访问静态字段。开发者需要更加谨慎,因为他在更高层次上工作。

序列化

单例本质上是离散的。这些单例通常不应该是可序列化的。将特定域的单例从一个虚拟机传输到另一个虚拟机在实践中没有意义;单例被定义为“在 VM 内唯一”,而不是“在宇宙中唯一”。

如果状态化的单例确实需要被序列化,它必须精确地、公开地定义在另一个虚拟机 (VM) 中反序列化这样的单例意味着什么,那里可能已经存在相同类型的单例。