单例模式有很多种,如:
饿汉式 线程安全,但是浪费资源;
懒汉式 懒汉式又分为,同步锁单例模式 性能较差;
双重判断同步锁单例模式
静态内部类单例模式
以上单例模式大多数都是基于代码层面来保证安全的,而枚举单例模式则是基于JVM机制来保证安全的,而且用法不要太简单。
废话不多说,上代码
枚举单例模式写法很简单吧
线程安全
它的线程安全是有枚举的特性保证的,初始化之后在JVM中只会存在一个实例。
反射安全
我们再来看下是否可以防止反射攻击。
我们可以看到当我们通过反射去创建一个新的实例时,JVM抛出了一个非法状态的异常,异常信息为:Cannot reflectively create enum objects
由这个信息我们可以获知到,枚举是不能通过反射创建的,这个是反射的机制保障的,那么它的机制是什么呢?
源码之下无秘密,debug走着
看来就是这里了,很好找
if ((clazz.getModifiers() & Modifier.ENUM) != 0)
throw new IllegalArgumentException("Cannot reflectively create enum objects")
直译就是如果当前要创建实例类的类修饰符与修饰符中的枚举修饰符常量值进行‘与’操作,操作的结果如果不为零则抛出该异常。
说人话就是如果当前要创建实例的类是枚举类,则抛出该异常。
修饰符常量值对于类型如下:
修饰符常量值使用的是位域--一种对内存极致利用的设计,很好很强大。有兴趣的可以去百度下,很简单。
上图中可以看到 clazz.getModifiers() = 16401 而 Modifier.ENUM = 16384 = 0x4000
16401 &16384 =16384,所以会抛出异常,没有问题。但是为什么clazz.getModifiers() = 16401呢?
其实java 底层中没有枚举这种类型,之所以我们可以直接定义枚举类只是语法糖罢了。它的底层还是翻译成了class.
我们通过jad反编译工具将我们刚刚写的枚举类编译成的class文件反编译成java文件,拆开语法糖的外衣,去掉它的伪装,看一下真实的枚举类代码的模样,然后看看能不能从中发现一下端倪,解开这个秘密。
如下:
通过反编译的生成的Java文件我们可以知道其实所谓的枚举类在二进制文件中就是一个普通的class类,该类继承了Enum类而已,然后为了保证枚举类不可继承的特性所以使用了final修饰符进行限制,wait!
Final 修饰符 + 枚举修饰符 + public修饰符 = 16 + 16384 + 1 = 16401 !!!
不错嘛,虽然藏在JVM的角落里,可依旧被我们找到了!
其实还有一个点,不知道小伙伴们有注意到没,在上面的我们打算通过反射取获取枚举的实例时,调用的构造器是个有参构造器
EnumMode.class.getDeclaredConstructor(String.class,int.class);
之所以用这个是有原因的,通过上面反编译的代码其实可以看出,枚举类拆掉语法糖之后的类和其他类有一点不同----是没有默认的无参构造器的,而是一个有参的构造器
private EnumMode(String s, int i){
super(s, i);
}
String 类型代表的是枚举的描述值,默认为枚举字段名值,int类型代表当前枚举在枚举类中的位置。
看到这里相信你已经对枚举单例为什么很安全应该已经理解了吧,如果是的话,那么恭喜你又多了一个无用的知识。