这个看起来是因为没有空的构造函数导致的,还并不能下定义说防御了反射攻击。那它有什么构造函数呢,可以看它的父类Enum类:
// @since 1.5 它是所有Enum类的父类,是个抽象类 public abstract class Enum<E extends Enum<E>> implements Comparable<E>, Serializable { // 这是它的唯一构造函数,接收两个参数(若没有自己额外指定构造函数的话~) protected Enum(String name, int ordinal) { this.name = name; this.ordinal = ordinal; } ... }
既然它有这个构造函数,那我们就先拿到这个构造函数再创建对象试试:
public class Main { public static void main(String[] args) throws Exception { EnumSingleton s = EnumSingleton.INSTANCE; // 拿到所有的构造函数,包括非public的 Constructor<EnumSingleton> constructor = EnumSingleton.class.getDeclaredConstructor(String.class, int.class);// 拿到有参的构造器 constructor.setAccessible(true); // 使用空构造函数new一个实例。即使它是private的~~~ System.out.println("拿到了构造器:" + constructor); EnumSingleton sReflection = constructor.newInstance("testInstance", 1); System.out.println(s); //com.fsx.bean.Singleton@1f32e575 System.out.println(sReflection); //com.fsx.bean.Singleton@279f2327 System.out.println(s == sReflection); // false } }
运行打印:
拿到了构造器:private com.fsx.bean.EnumSingleton(java.lang.String,int) Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objects at java.lang.reflect.Constructor.newInstance(Constructor.java:417) at com.fsx.maintest.Main.main(Main.java:22)
第一句输出了,表示我们是成功拿到了构造器Constructor对象的,只是在执行newInstance时候报错了。并且也提示报错在Constructor的417行,看看Constructor的源码处:
public final class Constructor<T> extends Executable { ... public T newInstance(Object ... initargs) throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { ... if ((clazz.getModifiers() & Modifier.ENUM) != 0) throw new IllegalArgumentException("Cannot reflectively create enum objects"); ... } ... }
主要是这一句:(clazz.getModifiers() & Modifier.ENUM) != 0。说明:反射在通过newInstance创建对象时,会检查该类**是否ENUM修饰**,如果是则抛出异常,反射失败,因此枚举类型对反射是绝对安全的。
那么,枚举对序列化、反序列化是否安全?
public class Main { public static void main(String[] args) { EnumSingleton s = EnumSingleton.INSTANCE; byte[] serialize = SerializationUtils.serialize(s); Object deserialize = SerializationUtils.deserialize(serialize); System.out.println(s == deserialize); //true } }
结果是:true。因此:枚举类型对序列化、反序列也是安全的。
综上,可以得出结论:枚举是实现单例模式的最佳实践。毕竟使用它全都是优点:
- 反射安全
- 序列化/反序列化安全
- 写法简单
- 没有一个更有信服力的原因不去使用枚举
附:局变量比单例模式差在哪里?
- 不可延迟实例化
- 不能保证全局只有一个实例(因为使用者都可以自己new对象)
总结
单例模式作为最为简单的一种设计模式,可以说是用到了everywhere,它不仅仅是我们撸码中肯定会用到的,更是面必问的一道题。我相信你理解了本文之后,以后不管使用和面试,都能轻松应对~