文章目录
- [80. Java 枚举类 - 使用枚举实现单例模式](#80. Java 枚举类 - 使用枚举实现单例模式)
-
- [**1️⃣ 为什么用枚举实现单例?**](#1️⃣ 为什么用枚举实现单例?)
- [**2️⃣ 枚举实现单例模式**](#2️⃣ 枚举实现单例模式)
- [**3️⃣ 枚举单例如何防止反射攻击?**](#3️⃣ 枚举单例如何防止反射攻击?)
- [**4️⃣ 枚举单例如何防止反序列化破坏?**](#4️⃣ 枚举单例如何防止反序列化破坏?)
- [**5️⃣ 枚举单例 vs 传统单例**](#5️⃣ 枚举单例 vs 传统单例)
- [**6️⃣ 枚举单例的适用场景**](#6️⃣ 枚举单例的适用场景)
- [**7️⃣ 扩展:枚举单例 + 接口**](#7️⃣ 扩展:枚举单例 + 接口)
- [**🔹 总结**](#🔹 总结)
- [**8️⃣ 总结**](#8️⃣ 总结)
-
- [**🎯 为什么推荐用枚举实现单例?**](#🎯 为什么推荐用枚举实现单例?)
- [**🎯 什么时候不要用枚举单例?**](#🎯 什么时候不要用枚举单例?)
80. Java 枚举类 - 使用枚举实现单例模式
在 Java 中,单例模式(Singleton Pattern) 是一种常见的设计模式,保证一个类只有一个实例,并提供全局访问点。
而 使用枚举(Enum)实现单例 是 最简单、最安全的方式! 🚀
1️⃣ 为什么用枚举实现单例?
传统的单例模式:
- 可能会被反射破坏 (通过
setAccessible(true)
访问私有构造方法)。 - 可能会被反序列化破坏(对象在反序列化时被重新创建)。
- 需要额外的代码保证线程安全。
枚举单例的优势:
✅ 防止反射攻击 (枚举构造方法是 private
且不能通过反射创建新实例)。
✅ 防止反序列化破坏 (枚举单例在反序列化时不会创建新实例)。
✅ 天然线程安全 (JVM
保证 enum
的实例创建是线程安全的)。
✅ 代码更简洁(比传统单例模式少很多代码)。
2️⃣ 枚举实现单例模式
java
public enum SomeSingleton {
INSTANCE; // 唯一实例
// 可以添加其他成员变量和方法
private int value;
public void setValue(int value) {
this.value = value;
}
public int getValue() {
return value;
}
public void doSomething() {
System.out.println("Doing something...");
}
}
public class SingletonTest {
public static void main(String[] args) {
// 获取单例实例
SomeSingleton singleton1 = SomeSingleton.INSTANCE;
SomeSingleton singleton2 = SomeSingleton.INSTANCE;
// 设置值
singleton1.setValue(42);
// 两个引用指向同一个对象
System.out.println(singleton1.getValue()); // 输出 42
System.out.println(singleton2.getValue()); // 也是 42
// 调用方法
singleton1.doSomething();
}
}
💡 输出
java
42
42
Doing something...
📌 代码解析
INSTANCE
是 唯一的实例 ,不会创建新的对象。setValue()
和getValue()
让我们可以存储数据(类似单例对象中的成员变量)。doSomething()
是一个普通方法,可以调用它做一些业务逻辑。
3️⃣ 枚举单例如何防止反射攻击?
在普通单例模式中,可以使用反射绕过私有构造方法:
java
Constructor<SomeSingleton> constructor = SomeSingleton.class.getDeclaredConstructor();
constructor.setAccessible(true);
SomeSingleton newInstance = constructor.newInstance(); // ❌ 破坏单例!
💥 错误
java
java.lang.NoSuchMethodException: SomeSingleton.<init>()
📌 枚举不会暴露构造方法 ,所以反射无法创建新实例!
4️⃣ 枚举单例如何防止反序列化破坏?
在普通单例模式中,反序列化可能会创建新实例:
java
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("singleton.ser"));
out.writeObject(singleton1);
out.close();
ObjectInputStream in = new ObjectInputStream(new FileInputStream("singleton.ser"));
SomeSingleton deserializedInstance = (SomeSingleton) in.readObject();
💡 如果是普通单例,会创建新对象! 但如果使用枚举,反序列化不会创建新实例:
java
SomeSingleton singleton1 = SomeSingleton.INSTANCE;
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("singleton.ser"));
out.writeObject(singleton1);
out.close();
ObjectInputStream in = new ObjectInputStream(new FileInputStream("singleton.ser"));
SomeSingleton singleton2 = (SomeSingleton) in.readObject();
System.out.println(singleton1 == singleton2); // ✅ 永远返回 true
📌 枚举的 readResolve()
方法会保证反序列化后还是同一个实例。
5️⃣ 枚举单例 vs 传统单例
方式 | 是否线程安全 | 反射攻击 | 反序列化安全 | 代码复杂度 |
---|---|---|---|---|
枚举单例 | ✅ 安全 | ✅ 防止 | ✅ 防止 | ⭐ 最简洁 |
懒汉式(synchronized) | ✅ 安全 | ❌ 有风险 | ❌ 有风险 | ❌ 代码复杂 |
双重检查锁(DCL) | ✅ 安全 | ❌ 有风险 | ❌ 有风险 | ❌ 代码复杂 |
静态内部类 | ✅ 安全 | ❌ 有风险 | ❌ 有风险 | ✅ 代码较简洁 |
💡 结论:
- 推荐使用枚举单例 ,因为它最安全,代码最少!
- 如果单例需要懒加载(Lazy Initialization) ,可以考虑 静态内部类。
6️⃣ 枚举单例的适用场景
✅ 适合使用枚举单例的情况
- 全局唯一对象(比如:日志管理器、配置管理、数据库连接池)。
- 需要防止反射和反序列化破坏的单例。
- 不需要懒加载(枚举单例是饿汉式的,类加载时就创建)。
❌ 不适合使用枚举单例的情况
- 如果需要懒加载 ,枚举 不支持延迟初始化 (可以用 静态内部类)。
- 如果需要继承其他类 ,枚举不能继承其他类(但可以实现接口)。
7️⃣ 扩展:枚举单例 + 接口
如果单例需要实现某些接口 ,枚举是可以实现的:
java
interface Service {
void execute();
}
public enum SingletonService implements Service {
INSTANCE;
@Override
public void execute() {
System.out.println("Executing service...");
}
}
public class EnumInterfaceTest {
public static void main(String[] args) {
SingletonService.INSTANCE.execute();
}
}
💡 输出
java
Executing service...
📌 解析
SingletonService
实现了Service
接口 ,并提供了execute()
方法。INSTANCE.execute()
可以直接调用接口方法。
🔹 总结
特点 | 枚举单例 |
---|---|
是否最安全 | ✅ 是(防反射 & 反序列化攻击) |
是否线程安全 | ✅ 是(JVM 保证) |
是否最简洁 | ✅ 是 (不用写 synchronized 、volatile ) |
是否支持懒加载 | ❌ 否(枚举是饿汉式单例) |
是否能继承类 | ❌ 不能(枚举不能继承类,但可以实现接口) |
💡 推荐使用枚举单例,除非需要懒加载! 🚀
8️⃣ 总结
🎯 为什么推荐用枚举实现单例?
✅ 代码最简单 ,不用写 synchronized
、volatile
✅ 线程安全 ,JVM 保证 enum
只会创建一个实例
✅ 防止反射攻击 ,枚举没有公开构造方法
✅ 防止反序列化攻击 ,不会创建新实例
✅ 可以实现接口,用于全局管理对象
🎯 什么时候不要用枚举单例?
❌ 需要懒加载 (用静态内部类更合适)
❌ 需要继承其他类(枚举不能继承类)
希望这个讲解能帮你彻底掌握 如何用枚举实现单例模式!🚀 🎯