zoukankan      html  css  js  c++  java
  • 使用枚举的正确姿势

    枚举是JDK1.5引入的新特性。被enum关键字修饰的类就是一个枚举类。

    关于枚举,阿里巴巴开发手册有这样两条建议:

    1. 枚举类名带上 Enum 后缀,枚举成员名称需要全大写,单词间用下划线隔开。
    2. 如果变量值仅在一个固定范围内变化用 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 {};
    }
    
    1. 枚举类被final修饰,因此枚举类不能被继承;
    2. 枚举类默认继承了Enum类,java不支持多继承,因此枚举类不能继承其他类;
    3. 枚举类的构造器是private修饰的,因此其他类不能通过构造器来获取对象;
    4. 枚举类的成员变量是static修饰的,可以用类名.变量来获取对象;
    5. values()方法是获取所有的枚举实例;
    6. valueOf(java.lang.String)是根据名称获取对应的实例;

    二 枚举创建线程安全的单例模式

    public enum  SingletonEnum {
        
        INSTANCE;
        
        public void doSomething(){
            // dosomething...
        }
    }
    

    这样一个单例模式就创建好了,通过SingletonEnum.INSTANCE来获取对象就可以了。

    2.1 序列化造成单例模式不安全

    一个类如果如果实现了序列化接口,则可能破坏单例。每次反序列化一个序列化的一个实例对象都会创建一个新的实例。

    枚举序列化是由JVM保证的,每一个枚举类型和定义的枚举变量在JVM中都是唯一的,在枚举类型的序列化和反序列化上,Java做了特殊的规定:在序列化时Java仅仅是将枚举对象的name属性输出到结果中,反序列化的时候则是通过java.lang.EnumvalueOf方法来根据名字查找枚举对象。同时,编译器是不允许任何对这种序列化机制的定制的并禁用了writeObjectreadObjectreadObjectNoDatawriteReplacereadResolve等方法,从而保证了枚举实例的唯一性。

    2.2 反射造成单例模式不安全

    通过反射强行调用私有构造器来生成实例对象,造成单例模式不安全。

    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,无法通过反射创建实例对象!

    三 通过枚举消除if/else

    假如要写一套加密接口,分别给小程序、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准时推送技术文章,让你的上班路不在孤独,而且每月还有送书活动,助你提升硬实力!

  • 相关阅读:
    MFC listcontrol 分列 添加行数据 点击列头排序
    MFC 设置控件事件对应的函数
    MFC CString to char* (Visual Studio 2015 亲测可用)
    MFC MessageBox AfxMessageBox
    iOS 注意事项
    iOS instancetype or id ?
    iOS Aspect Fit,Aspect Fill,Scale To Fill
    iOS UIImageView用代码添加点击事件
    iOS 安装Cocoapods以及安装第三方库的操作流程
    iOS 获取当前用户的用户路径并写入文件
  • 原文地址:https://www.cnblogs.com/zhixie/p/13695438.html
Copyright © 2011-2022 走看看