java enum关键字

Written with StackEdit.

为什么思考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类进行序列化和反序列化
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值