枚举是JDK1.5引入的新特性。被enum
关键字修饰的类就是一个枚举类。
关于枚举,阿里巴巴开发手册有这样两条建议:
创建一个ColorEnum
的枚举类,通过编译,再反编译看看它发生了哪些变化。
public enum ColorEnum { RED,GREEN,BULE; }
使用命令javac ColorEnum.java
进行编译生成class文件,然后再用命令javap -p ColorEnum.class
进行反编译。
去掉包名,反编译后的内容如下:
public final class ColorEnum extends Enum{ public static final ColorEnum GREEN; public static final ColorEnum BULE; private static final ColorEnum[] $VALUES; public static ColorEnum[] values(); public static ColorEnum valueOf(java.lang.String); private ColorEnum(); static {}; }
final
修饰,因此枚举类不能被继承;Enum
类,java不支持多继承,因此枚举类不能继承其他类;private
修饰的,因此其他类不能通过构造器来获取对象;static
修饰的,可以用类名.变量来获取对象;public enum SingletonEnum { INSTANCE; public void doSomething(){ // dosomething... } }
这样一个单例模式就创建好了,通过SingletonEnum.INSTANCE
来获取对象就可以了。
一个类如果如果实现了序列化接口,则可能破坏单例。每次反序列化一个序列化的一个实例对象都会创建一个新的实例。
枚举序列化是由JVM
保证的,每一个枚举类型和定义的枚举变量在JVM
中都是唯一的,在枚举类型的序列化和反序列化上,Java做了特殊的规定:在序列化时Java仅仅是将枚举对象的name属性输出到结果中,反序列化的时候则是通过java.lang.Enum
的valueOf
方法来根据名字查找枚举对象。同时,编译器是不允许任何对这种序列化机制的定制的并禁用了writeObject
、readObject
、readObjectNoData
、writeReplace
和readResolve
等方法,从而保证了枚举实例的唯一性。
通过反射强行调用私有构造器来生成实例对象,造成单例模式不安全。
Class<?> aClass = Class.forName("xx.xx.xx"); Constructor<?> constructor = aClass.getDeclaredConstructor(String.class); SingletonEnum singleton = (SingletonEnum) constructor.newInstance("Java旅途");
但是使用枚举创建的单例完全不用考虑这个问题,来看看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); } } // 如果是枚举类型,直接抛出异常,不让创建实例对象! 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; }
如果是enum
类型,则直接抛出异常Cannot reflectively create enum objects
,无法通过反射创建实例对象!
假如要写一套加密接口,分别给小程序、app和web端来使用,但是这三种客户端的加密方式不一样。一般情况下我们会传一个类型type
来判断来源,然后调用对应的解密方法即可。代码如下:
if("WEIXIN".equals(type)){ // dosomething }else if("APP".equals(type)){ // dosomething }else if("WEB".equals(type)){ // dosomething }
现在使用枚举来消除这些if/else。
写一个加密用的接口,有加密和解密两个方法。然后用不同的算法去实现这个接口完成加解密。
public interface Util { // 解密 String decrypt(); // 加密 String encrypt(); }
创建一个枚举类来实现这个接口
public enum UtilEnum implements Util { WEIXIN { @Override public String decrypt() { return "微信解密"; } @Override public String encrypt() { return "微信加密"; } }, APP { @Override public String decrypt() { return "app解密"; } @Override public String encrypt() { return "app加密"; } }, WEB { @Override public String decrypt() { return "web解密"; } @Override public String encrypt() { return "web加密"; } }; }
最后,获取到type后,直接调用解密方法就行了。
String decryptMessage = UtilEnum.valueOf(type).decrypt();
以后,如果新增了一个其他加密方式,只需要修改上面的枚举类就完成了,业务代码都不需要改动。
这就是枚举类比较高级的两个用法。
如果觉得文章不错,欢迎关注、点赞、收藏,你们的支持是我创作的动力,感谢大家。
如果文章写的有问题,请不要吝啬,欢迎留言指出,我会及时核查修改。
如果你还想更加深入的了解我,可以微信搜索「Java旅途」进行关注。回复「1024」即可获得学习视频及精美电子书。每天7:30准时推送技术文章,让你的上班路不在孤独,而且每月还有送书活动,助你提升硬实力!