枚举单例属于注册式单例,注册式单例又称为登记式单例,就是将每一个实例都登记到某一个地方,使用唯一的标识获取实例。
我们先来一个枚举式单例的代码:
public enum EnumSingleton {
INSTANCE;
// 单例的成员变量
private Object data;
// 单例的成员方法
// 调用方式是 EnumSingleton.getInstance().getData()
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
// 通过 getInstance() 获取单例对象
// 调用方式是 EnumSingleton.getInstance()
public static EnumSingleton getInstance(){
return INSTANCE;
}
}
我们先来看看枚举实现单例的原理。
通过 Java 反编译工具 Jad(下载地址:https://varaneckas.com/jad)对上面的 EnumSingleton.class 进行反编译,
可以看到多了如下代码:
static
{
INSTANCE = new EnumSingleton("INSTANCE", 0);
$VALUES = (new EnumSingleton[] {
INSTANCE
});
}
所以,枚举式单例实际上是在静态代码块中就给 INSTANCE 进行了赋值,是饿汉式单例的实现。
在前面的文章,我们介绍了反射破坏单例,以及序列化破坏单例的问题
- 反射破坏单例问题 -> 优化私有构造器,判断对象是否已经完成创建
- 序列化破坏单例问题 -> 添加 readResolve() 方法,返回当前单例对象
=> 枚举单例的优越性:在代码简洁且不用做任何多余操作的前提下,解决了反射破坏单例问题,以及序列化破坏单例问题。
1.解决反射破坏单例问题
先来看一段测试代码:
public static void main(String[] args) {
try {
Class clazz = EnumSingleton.class;
// 拿到构造器
Constructor c = clazz.getDeclaredConstructor();
// 直接通过构造器创建对象
c.newInstance();
}catch (Exception e){
e.printStackTrace();
}
}
运行结果如下:

可以看到,报的是 java.lang.NoSuchMethodException 异常,意思是没找到无参的构造方法。这时候,我们打开 java.lang.Enum 的源码代码,查看它的构造方法,只有一个 protected 的构造方法,代码如下:
protected Enum(String name, int ordinal) {
this.name = name;
this.ordinal = ordinal;
}
既然拿空参构造器不行,那我们再来做一个这样的测试:
public static void main(String[] args) {
try {
Class clazz = EnumSingleton.class;
Constructor c = clazz.getDeclaredConstructor(String.class,int.class);
c.setAccessible(true);
// 通过枚举创
EnumSingleton enumSingleton = (EnumSingleton)c.newInstance("Jack",666);
}catch (Exception e){
e.printStackTrace();
}
}
运行结果如下:
这时错误已经非常明显了,告诉我们 Cannot reflectively create enum objects,不能用反射来创建枚举类型。
原理分析
还是习惯性地来看看 JDK 源码,进入 Constructor 的 newInstance()方法:
public T newInstance(Object ... initargs)
throws InstantiationException, IllegalAccessException,
IllegalArgumentException, InvocationTargetException
{
if (!override) {
if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
Class<?> caller = Reflection.getCallerClass();
checkAccess(caller, clazz, null, modifiers);
}
}
// 如果修饰符是 Modifier.ENUM 枚举类型,直接抛出异常
if ((clazz.getModifiers() & Modifier.ENUM) != 0)
throw new IllegalArgumentException("Cannot reflectively create enum objects");
ConstructorAccessor ca = constructorAccessor; // read volatile
if (ca == null) {
ca = acquireConstructorAccessor();
}
@SuppressWarnings("unchecked")
T inst = (T) ca.newInstance(initargs);
return inst;
}
到这为止,我们是不是已经非常清晰明了呢?
枚举式单例也是《Effective Java》书中推荐的一种单例实现写法。在 JDK 枚举的语法特殊性,以及反射也为枚举保驾护航,让枚举式单例成为一种比较优雅的实现。
2.解决序列化破坏单例问题
一样的,先来看一段测试代码:
public class EnumSingletonTest {
public static void main(String[] args) {
try {
EnumSingleton instance1 = null;
EnumSingleton instance2 = EnumSingleton.getInstance();
instance2.setData(new Object());
FileOutputStream fos = new FileOutputStream("EnumSingleton.obj");
ObjectOutputStream oos = new ObjectOutputStream(fos);
// 序列化
oos.writeObject(instance2);
oos.flush();
oos.close();
FileInputStream fis = new FileInputStream("EnumSingleton.obj");
ObjectInputStream ois = new ObjectInputStream(fis);
// 反序列化
instance1 = (EnumSingleton) ois.readObject();
ois.close();
System.out.println(instance1.getData());
System.out.println(instance2.getData());
System.out.println(instance1.getData() == instance2.getData());
}catch (Exception e){
e.printStackTrace();
}
}
}
运行结果如下;
原理分析
我们不妨再来看一下 JDK 源码,进入 ObjectInputStream 类的 readObject()方法,代码如下:
public final Object readObject()
throws IOException, ClassNotFoundException
{
if (enableOverride) {
return readObjectOverride();
}
// if nested read, passHandle contains handle of enclosing object
int outerHandle = passHandle;
try {
// readObject0()
Object obj = readObject0(false);
handles.markDependency(outerHandle, passHandle);
ClassNotFoundException ex = handles.lookupException(passHandle);
if (ex != null) {
throw ex;
}
if (depth == 0) {
vlist.doCallbacks();
}
return obj;
} finally {
passHandle = outerHandle;
if (closed && depth == 0) {
clear();
}
}
}
可以发现,在 readObject() 中又调用了 readObject0() 方法。进入readObject0() 方法,代码如下:
private Object readObject0(boolean unshared) throws IOException {
...
case TC_ENUM:
return checkResolve(readEnum(unshared));
...
}
我们看到在 readObject0() 中调用了 readEnum()方法,来看 readEnum() 中代码实现:
private Enum<?> readEnum(boolean unshared) throws IOException {
if (bin.readByte() != TC_ENUM) {
throw new InternalError();
}
ObjectStreamClass desc = readClassDesc(false);
if (!desc.isEnum()) {
throw new InvalidClassException("non-enum class: " + desc);
}
int enumHandle = handles.assign(unshared ? unsharedMarker : null);
ClassNotFoundException resolveEx = desc.getResolveException();
if (resolveEx != null) {
handles.markException(enumHandle, resolveEx);
}
String name = readString(false);
Enum<?> result = null;
Class<?> cl = desc.forClass();
if (cl != null) {
try {
@SuppressWarnings("unchecked")
// 通过枚举的类型,名称获取到具体枚举实例
Enum<?> en = Enum.valueOf((Class)cl, name);
result = en;
} catch (IllegalArgumentException ex) {
throw (IOException) new InvalidObjectException(
"enum constant " + name + " does not exist in " +
cl).initCause(ex);
}
if (!unshared) {
handles.setObject(enumHandle, result);
}
}
handles.finish(enumHandle);
passHandle = enumHandle;
return result;
}
可以看到,枚举类型其实通过类名和 Class 对象类找到一个唯一的枚举对象,因此,枚举对象不可能被类加载器加载多次。
枚举式单例也是《Effective Java》书中推荐的一种单例实现写法。在 JDK 枚举的语法特殊性,以及反射也为枚举保驾护航,让枚举式单例成为一种比较优雅的实现。








还没有评论,来说两句吧...