枚举类型天然的可序列化机制,能够强有力的保证不会出现多次实例化的情况,即使在复杂的序列化或者反射攻击的情况下,枚举类型的单例模式都没有问题。枚举类型的单例可能是实现单例模式中的最佳实践,《Effcetive Java》这本书也是强力推荐这种写法。
1、枚举实现单例模式
public enum EnumInstance {
INSTANCE;
private Object data;
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
public static EnumInstance getInstance(){
return INSTANCE;
}
}
2、序列化测试
EnumInstance enumInstance = EnumInstance.getInstance();
enumInstance.setData(new Object());
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("singleton_file"));
oos.writeObject(enumInstance);
File file = new File("singleton_file");
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
EnumInstance newEnumSingleton = (EnumInstance) ois.readObject();
System.out.println(enumInstance.getData());
System.out.println(newEnumSingleton.getData());
System.out.println(enumInstance.getData()==newEnumSingleton.getData());
输出结果:
java.lang.Object@2f4d3709
java.lang.Object@2f4d3709
true
在ObjectInputStream类中有一个readEnum方法,通过readString方法获取到枚举对象的名称,再通过类型和名称来获取常量,因为枚举中的名称是唯一的,对应一个枚举常量,所以获取的常量一定是唯一的常量对象,这样就没有创建新的对象,维持了这个类的单例属性。
/**
* Reads in and returns enum constant, or null if enum type is
* unresolvable. Sets passHandle to enum constant's assigned handle.
*/
private Enum<?> readEnum(boolean unshared) throws IOException {
// 校验部分代码已除去
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;
}
3、反射测试
Class objectClass = EnumInstance.class;
Constructor declaredConstructor = objectClass.getDeclaredConstructor(String.class, int.class);
// 修改私有构造器的访问权限
declaredConstructor.setAccessible(true);
EnumInstance enumInstance = (EnumInstance) declaredConstructor.newInstance("singleton", 1);
执行上述代码会抛出如下异常:java.lang.IllegalArgumentException: Cannot reflectively create enum objects,表示不同通过反射来创建枚举类的对象,在Constructor类中的newInstance()方法中有下列代码,当检测出类型为枚举类型,即抛出异常。
if ((clazz.getModifiers() & Modifier.ENUM) != 0){
throw new IllegalArgumentException("Cannot reflectively create enum objects");
}