三、枚举类实现单例(解决反射安全问题)
查看源码
为什么我们通过使用枚举类能够实现单例呢?
通过看反射方法newInstance()(Constructor类)的源码。 @CallerSensitive 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); } } //如果是枚举类型直接,直接抛出异常,不让创建实例对象 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; }
通过源码我们可以知道对于枚举类型无法使用newInstance来创建实例,也就能够解决单例在反射中的安全问题!
测试过程
见single目录下的OwnStudent:这是后来补上去的仅仅是类名不相同而已
自定义枚举类:
public enum Student { STUDENT; }
我们来实验一下即可知道了,首先来若是想要通过反射来newInstance首先需要获取Constructor构造器实例,所以我们最起码得先知道其构造器的参数以及类型吧,我们通过反编译Student.class文件来查看一下:
①IDEA中的反编译
public enum Student { STUDENT; private Student() { } }
②JDK工具javap进行反编译
③使用jad工具来进行反编译,获取一个java文件如下:
public final class Student extends Enum { public static Student[] values() { return (Student[])$VALUES.clone(); } public static Student valueOf(String name) { return (Student)Enum.valueOf(xyz/changlu/Student, name); } private Student(String s, int i) { super(s, i); } public static final Student STUDENT; private static final Student $VALUES[]; static { STUDENT = new Student("STUDENT", 0); $VALUES = (new Student[] { STUDENT }); } }
测试程序:
检测IDEA中的私有无参构造创建实例:
class Test1{ public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { //通过Student的字节码类来获取构造器(空参) Constructor<Student> constructor = Student.class.getDeclaredConstructor(); constructor.setAccessible(true); System.out.println(constructor.newInstance()); } }
通过检验我们能够知道IDEA中的反编译结果并不正确的!
IDEA中反编译出来的实际上有问题的,在枚举类中创建实例并不是通过无参构造来创建的,我们以jad工具反编译出的结果为准,是通过一个私有的有参构造来创建实例的。
class Test1{ public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { //获取单例 System.out.println(Student.STUDENT); //传入参数获取无参构造 Constructor<Student> constructor = Student.class.getDeclaredConstructor(String.class,int.class); constructor.setAccessible(true); System.out.println(constructor.newInstance()); } }
这个报错结果才应该出现的情况,与我们之前在newInstance()源码中抛出的异常结果相同!
总结
1、单例模式优点是减少内存系统开销,保证单个类只有一个实例;缺点是扩展困难,并发测试不利于调试,容易违背单一职责原则。
2、实现单例主要有三种方式:①饿汉式(线程安全的);②懒汉式(双重加锁+volatile);③内部静态类。通过反射技术上面三种方式都容易出现安全问题!
3、通过枚举类实现单例来解决反射带来的安全问题,因为在反射技术中newInstance()方法对于枚举类是无法进行实例化的!!!