美文网首页程序员
java enum关键字

java enum关键字

作者: 小明同学_Random | 来源:发表于2019-12-09 10:19 被阅读0次

    为什么思考enum关键字

    在思考enum之前,同事问了我关于enum的两个问题:

    1. enum 类可以继承吗?
    2. vo里使用了enum,怎么改造呢?

    回答这两个问题

    1. enum 类是不能继承的,我记得enum 类是final语义的
    2. vo里最好不要存在enum,或者说接口层面不要有enum的直接序列化和反序列化,这个会涉及到扩展问题,如果是vo的话,恐怕没法保证接口透明下去改造了

    思考

    1. enum的final的语义是怎么实现的呢?
    2. enum是怎么实现线程安全的单例模式?
    3. enum作为接口定义会出现的扩展问题?
    4. enum的set和map为什么特殊化?
    5. enum的特殊需要注意的地方?

    调查和研究

    enum的final的语义是怎么实现的呢?

    说实话,最初找网上资料都是看反编译的Season enum类,代码如下:

    public enum Season {
        SPRING("春天"), SUMMER("夏天"), AUTUMN("秋天"), WINTER("冬天");
        
        private final String chinese;
        
        private Season(String chinese) {
            this.chinese = chinese;
        }
    
        public String getChinese() {
            return chinese;
        }
    }
    

    然后他们进行编译和反编译的结果如下:

    public final class Season extends Enum<Season> {
        public static final Season SPRING;
        public static final Season SUMMER;
        public static final Season AUTUMN;
        public static final Season WINTER;
        private static final Season[] ENUM$VALUES;
        
        static {
            SPRING = new Season("SPRING", 0, "春天");
            SUMMER = new Season("SUMMER", 1, "夏天");
            AUTUMN = new Season("AUTUMN", 2, "秋天");
            WINTER = new Season("WINTER", 3, "冬天");
            ENUM$VALUES = new Season[]{SPRING, SUMMER, AUTUMN, WINTER}
        }
        
        private final String chinese;
        
        private Season(String name, int ordinal, String chinese) {
            super(name, ordinal);
            this.chinese = chinese;
        }
    
        public String getChinese() {
            return chinese;
        }
        
        public static Season[] values() {
            Season[] arr = new Session[ENUM$VALUES.length];
            System.arraycopy(ENUM$VALUES, 0, arr, 0, arr.length);
            return arr;
        }
        
        public static Season valueOf(String name) {
            return Enum.valueOf(Season.class, name);
        }
    }
    

    大量的case都是这么一个model,我也不知道从哪抄过来的。。。
    然后我自己进行了调研,返现反编译的结果如下(jdk1.8):

    public enum Season {  
      SPRING("春天"),  
      SUMMER("夏天"),  
      AUTUMN("秋天"),  
      WINTER("冬天");  
      
     private final String chinese;  
      
     private Season(String chinese) {  
            this.chinese = chinese;  
      }  
      
        public String getChinese() {  
            return this.chinese;  
      }  
    }
    

    很明显,对于我的反编译版本,已经对enum进行了关键字处理和优化
    然后我就需要印证网上的资料正确性,首先我去了Stack Overflow里调查,发现确实是这样的回答,在一定程度上说明,这样的答案并不是空穴来风
    然后我去看类依赖,发现Season虽然并没有写extends,但是确实继承了Enum类,也就是说,编译器会对我们enum关键字定义的class进行特定化的改造。
    然后就需要查看Enum类,发现Enum是个abstract类,里面对enum进行了域定义和存储定义,从Enum类看出确实符合网友给的的结果
    结论:enum是有final语义的关键字

    enum是怎么实现线程安全的单例模式?

    从反编译的代码可以看出,enum类使用了类加载保证了线程安全,使用static代码块对每个枚举进行了初始化,保证单例。这也是最推荐的单例写法之一

    enum作为接口定义会出现的扩展问题?

    根据alibaba的java代码规范里可以看到有这么一条:

    【强制】 二方库里可以定义枚举类型,参数可以使用枚举类型,但是接口返回值不允许使用
    枚举类型或者包含枚举类型的 POJO 对象

    对于enum类确实不适合作为序列化和反序列化的对象定义,大致思考能够想到:扩展性极差,如果约定了enum为借口对象,那当我们想对enum进行对象扩展的时候,发现很有可能出现不兼容的问题,比如有一个server端的枚举类:

    @AllArgsConstructor  
    @Getter  
    public enum HttpRequestMethodEnum {  
      
      GET(1, "GET", "get请求"),  
      HEAD(3, "HEAD", "HEAD请求"),  
      POST(2, "POST", "post请求");  
      
      
      /**  
     * code,标识,没有含义  
      */  
      private final int code;  
      
      /**  
     * http的请求方式名称,RequestBuilder 使用name的方式进行识别request method  
     */  private final String name;  
      
      /**  
     * desc */  private final String desc;  
      
    }
    

    接口传输使用的json序列化的字符串,client将对字符串进行发序列化,反序列化的对象类如下:

    @AllArgsConstructor  
    @Getter  
    public enum HttpRequestMethodEnum2 {  
      
        GET(1, "GET", "get请求"),  
      POST(2, "POST", "post请求");  
      
      /**  
     * code,标识,没有含义  
      */  
      private final int code;  
      
      
      /**  
     * http的请求方式名称,RequestBuilder 使用name的方式进行识别request method  
     */  private final String name;  
      
      /**  
     * desc */  private final String desc;  
      
    }
    

    很明显,这就是常见的二方包升级导致的版本不一致的情况,测试:

    public class HttpRequestMethodEnumTest {  
      
        @Test  
        public void test1() {  
        final HttpRequestMethodEnum post = HttpRequestMethodEnum.POST;  
        final String s = JacksonUtils.encode2String(post);  
        System.out.println(s);  
        HttpRequestMethodEnum2 post2 = JacksonUtils.decodeFromString(s, HttpRequestMethodEnum2.class);  
        System.out.println(post2);  
      }  
      
        @Test  
        public void test2() {  
        final HttpRequestMethodEnum head = HttpRequestMethodEnum.HEAD;  
        final String s = JacksonUtils.encode2String(head);  
        System.out.println(s);  
        HttpRequestMethodEnum2 head2 = JacksonUtils.decodeFromString(s, HttpRequestMethodEnum2.class);  
        System.out.println(head2);  
      }  
     
    }
    

    发现test1是没有问题的,但是test2就会抛出异常来,解析jackson的反序列流程:

    1. 首先创建反序列化器,从BasicDeserializerFactory中选择构造Enum的反序列化器
    public JsonDeserializer<?> createEnumDeserializer(DeserializationContext ctxt,  
      JavaType type, BeanDescription beanDesc)  
        throws JsonMappingException  
    {  
        final DeserializationConfig config = ctxt.getConfig();  
     final Class<?> enumClass = type.getRawClass();  
      // 23-Nov-2010, tatu: Custom deserializer?  
      JsonDeserializer<?> deser = _findCustomEnumDeserializer(enumClass, config, beanDesc);  
      
     if (deser == null) {  
            ValueInstantiator valueInstantiator = _constructDefaultValueInstantiator(ctxt, beanDesc);  
      SettableBeanProperty[] creatorProps = (valueInstantiator == null) ? null  
      : valueInstantiator.getFromObjectArguments(ctxt.getConfig());  
      // May have @JsonCreator for static factory method:  
      for (AnnotatedMethod factory : beanDesc.getFactoryMethods()) {  
                if (_hasCreatorAnnotation(ctxt, factory)) {  
                    if (factory.getParameterCount() == 0) { // [databind#960]  
      deser = EnumDeserializer.deserializerForNoArgsCreator(config, enumClass, factory);  
     break;  }  
                    Class<?> returnType = factory.getRawReturnType();  
      // usually should be class, but may be just plain Enum<?> (for Enum.valueOf()?)  
      if (returnType.isAssignableFrom(enumClass)) {  
                        deser = EnumDeserializer.deserializerForCreator(config, enumClass, factory, valueInstantiator, creatorProps);  
     break;  }  
                }  
            }  
             
            // Need to consider @JsonValue if one found  
            // 将会在这个地方构造EnumDeserializer,这个构造器我们会发现传入了一个EnumResolver,而在后面的反序列化过程中发现将会先由该EnumResolver将枚举class的基本信息进行解析存储
      if (deser == null) {  
                deser = new EnumDeserializer(constructEnumResolver(enumClass,  
      config, beanDesc.findJsonValueAccessor()),  
      config.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS));  
      }  
        }  
      
        // and then post-process it too  
      if (_factoryConfig.hasDeserializerModifiers()) {  
            for (BeanDeserializerModifier mod : _factoryConfig.deserializerModifiers()) {  
                deser = mod.modifyEnumDeserializer(config, type, beanDesc, deser);  
      }  
        }  
        return deser;  
    }
    
    1. 接着我们看下EnumResolver的构造过程会发现在调用 constructFor 方法的时候进行了enumValues的解析和存储:
    public static EnumResolver constructFor(Class<Enum<?>> enumCls, AnnotationIntrospector ai)  
    {  
        // 这里会调用Class类的getEnumConstantsShared方法,这个方法会invoke方法Values(),Values()方法会返回所有的枚举实例,所以在这里就拿到所有的枚举实例
        Enum<?>[] enumValues = enumCls.getEnumConstants();  
     if (enumValues == null) {  
            throw new IllegalArgumentException("No enum constants for class "+enumCls.getName());  
      }  
        String[] names = ai.findEnumValues(enumCls, enumValues, new String[enumValues.length]);  
      HashMap<String, Enum<?>> map = new HashMap<String, Enum<?>>();  
     for (int i = 0, len = enumValues.length; i < len; ++i) {  
            String name = names[i];  
     if (name == null) {  
                name = enumValues[i].name();  
      }  
            map.put(name, enumValues[i]);  
      }  
      
        Enum<?> defaultEnum = ai.findDefaultEnumValue(enumCls);  
     // 这里已经把enumValues 转成了map,map的key是抽象类Enum中的name,也就是我们enum类中的实例名字,value就是我们的实例对象
     return new EnumResolver(enumCls, enumValues, map, defaultEnum);  
    }
    
    1. 然后我们再看EnumDeserializer的构造方法:
    public EnumDeserializer(EnumResolver byNameResolver, Boolean caseInsensitive)  
    {  
        super(byNameResolver.getEnumClass());  
      _lookupByName = byNameResolver.constructLookup();  
      _enumsByIndex = byNameResolver.getRawEnums();  
      _enumDefaultValue = byNameResolver.getDefaultValue();  
      _caseInsensitive = caseInsensitive;  
    }
    

    byNameResolver.constructLookup()方法其实就是把枚举的values内容重新封装返回,放到了 EnumDeserializer 的 _lookupByName 域中

    1. 以上是对 EnumDeserializer 的构造过程算是完成了,DeserializerFactory构造出了EnumDeserializer之后,不用说,肯定是要用这个反序列化器反序列化了,也就是在ObjectMapper中的:
    protected Object _readMapAndClose(JsonParser p0, JavaType valueType)  
        throws IOException  
    {  
        try (JsonParser p = p0) {  
            Object result;  
      JsonToken t = _initForReading(p, valueType);  
     final DeserializationConfig cfg = getDeserializationConfig();  
     final DeserializationContext ctxt = createDeserializationContext(p, cfg);  
     if (t == JsonToken.VALUE_NULL) {  
                // Ask JsonDeserializer what 'null value' to use:  
      result = _findRootDeserializer(ctxt, valueType).getNullValue(ctxt);  
      } else if (t == JsonToken.END_ARRAY || t == JsonToken.END_OBJECT) {  
                result = null;  
      } else {  
                // 寻找RootDeserializer并构造Deserializer
                JsonDeserializer<Object> deser = _findRootDeserializer(ctxt, valueType);  
     if (cfg.useRootWrapping()) {  
                    result = _unwrapAndDeserialize(p, ctxt, cfg, valueType, deser);  
      } else {  
                    // 调用反序列的反序列方法
                    result = deser.deserialize(p, ctxt);  
      }  
                ctxt.checkUnresolvedObjectId();  
      }  
            if (cfg.isEnabled(DeserializationFeature.FAIL_ON_TRAILING_TOKENS)) {  
                _verifyNoTrailingTokens(p, ctxt, valueType);  
      }  
            return result;  
      }  
    }
    
    1. 紧接着肯定要看EnumDeserializer.deserialize方法:
    public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException  
    {  
        JsonToken curr = p.getCurrentToken();  
      // Usually should just get string value:  
      if (curr == JsonToken.VALUE_STRING || curr == JsonToken.FIELD_NAME) {  
            // 获取lookup,也就是 _lookupByName
            CompactStringObjectMap lookup = ctxt.isEnabled(DeserializationFeature.READ_ENUMS_USING_TO_STRING)  
                    ? _getToStringLookup(ctxt) : _lookupByName;  
     final String name = p.getText();  
      //根据name进行查询实例
      Object result = lookup.find(name);  
     if (result == null) {  
                return _deserializeAltString(p, ctxt, lookup, name);  
      }  
            return result;  
      }  
        // But let's consider int acceptable as well (if within ordinal range)  
      if (curr == JsonToken.VALUE_NUMBER_INT) {  
            // ... unless told not to do that  
      int index = p.getIntValue();  
     if (ctxt.isEnabled(DeserializationFeature.FAIL_ON_NUMBERS_FOR_ENUMS)) {  
                return ctxt.handleWeirdNumberValue(_enumClass(), index,  
      "not allowed to deserialize Enum value out of number: disable DeserializationConfig.DeserializationFeature.FAIL_ON_NUMBERS_FOR_ENUMS to allow"  
      );  
      }  
            if (index >= 0 && index < _enumsByIndex.length) {  
                return _enumsByIndex[index];  
      }  
            if ((_enumDefaultValue != null)  
                    && ctxt.isEnabled(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_USING_DEFAULT_VALUE)) {  
                return _enumDefaultValue;  
      }  
            if (!ctxt.isEnabled(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL)) {  
                return ctxt.handleWeirdNumberValue(_enumClass(), index,  
      "index value outside legal index range [0..%s]",  
      _enumsByIndex.length-1);  
      }  
            return null;  
      }  
        return _deserializeOther(p, ctxt);  
    }
    

    通过这个地方不难看出我们可以通过name进行获取出Enum实例来了,如果我们方序列的Enum中没有改name,就会失败。

    enum的set和map为什么特殊化?

    这里我们只研究EnumMap

    1. 首先来看EnumMap的根据类型的构造函数:
    public EnumMap(Class<K> keyType) {  
      this.keyType = keyType;  
      keyUniverse = getKeyUniverse(keyType);  
      vals = new Object[keyUniverse.length];  
    }
    

    通过这三个参数也可以看到,首先会约定我们的枚举类型(keyType ),然后会把该枚举的values存起来(keyUniverse ),然后创建了一个存放values的数据vals,这个数组大小和枚举values的长度是一样的,不难想到,最终是要通过下标来做映射关系

    1. 然后我们看下put方法:
    public V put(K key, V value) {  
      // check key的类型是否是我们的枚举类型
      typeCheck(key);  
      //取出下标
     int index = key.ordinal();  
      Object oldValue = vals[index];  
      //把我们的值塞入到vals数组的指定位置
      vals[index] = maskNull(value);  
      //如果之前没有值,就说明我们的map元素增加了一个,size++
     if (oldValue == null)  
            size++;  
     // 将值返回回去
     return unmaskNull(oldValue);  
    }
    
    1. 紧接着我们看看get方法
    public V get(Object key) {  
        return (isValidKey(key) ?  
                unmaskNull(vals[((Enum<?>)key).ordinal()]) : null);  
    }
    

    首先校验key的类型是不是我们限定的枚举类型,然后找到对应下标的vals[]的值

    enum的特殊需要注意的地方

    1. 上面介绍了EnumMap,肯定会对Enum的ordinal感兴趣,接口文档明确表示,这个字段不会被使用,主要的使用地方实在设计师对EnumSet和EnumMap的设计,言外之意如果大家对自己的设计没有想明白的时候,还是不要使用这个字段为好
    2. Enum类并没有Values() 和 valueOf(String) 方法,这两个方法时编译的时候生成的,这个感觉还是挺烦人的
    3. Enum虽然实现了 Serializable 接口,但是却不可以使用默认的反序列化方式
    /**  
     * prevent default deserialization */
    private void readObject(ObjectInputStream in) throws IOException,  
      ClassNotFoundException {  
        throw new InvalidObjectException("can't deserialize enum");  
    }  
      
    private void readObjectNoData() throws ObjectStreamException {  
        throw new InvalidObjectException("can't deserialize enum");  
    }
    

    这是因为Enum类是单例的,反序列化会实例化一个对象,这违反单例的设计,所以Enum虽然可以被java 序列化,但是却不可以反序列化。

    总结

    1. enum是一种很好的单例模式,也是一种很好的类型列举方式,不过并不是所有的地方都适用
    2. enum有很多的内容是编译器补充的,比如values()方法和valueOf(String name)方法
    3. enum尽量在自己系统内部做定义,尽量不要在接口层交互直接对enum类进行序列化和反序列化

    相关文章

      网友评论

        本文标题:java enum关键字

        本文链接:https://www.haomeiwen.com/subject/clcbgctx.html