Coder,我怀疑你并不会枚举

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

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

  1. 枚举类名带上 Enum 后缀,枚举成员名称须要全大写,单词间用下划线隔开。算法

  2. 若是变量值仅在一个固定范围内变化用 enum 类型来定义。sql

一 枚举类有哪些特色

建立一个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不支持多继承,所以枚举类不能继承其余类;app

  3. 枚举类的构造器是private修饰的,所以其余类不能经过构造器来获取对象;ide

  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();

之后,若是新增了一个其余加密方式,只须要修改上面的枚举类就完成了,业务代码都不须要改动。

这就是枚举类比较高级的两个用法。


< END >

往期精选
  揭开链表的真面目
  揭开数组的真面目
  聊聊Mysql中的int(1)
  如何有效防止SQL注入攻击
《RabbitMQ》如何保证消息不被重复消费
《RabbitMQ》如何保证消息的可靠性

本文分享自微信公众号 - Java旅途(Javatrip)。
若有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一块儿分享。

相关文章
相关标签/搜索