为什么思考enum关键字
在思考enum之前,同事问了我关于enum的两个问题:
- enum 类可以继承吗?
- vo里使用了enum,怎么改造呢?
回答这两个问题
- enum 类是不能继承的,我记得enum 类是final语义的
- vo里最好不要存在enum,或者说接口层面不要有enum的直接序列化和反序列化,这个会涉及到扩展问题,如果是vo的话,恐怕没法保证接口透明下去改造了
思考
- enum的final的语义是怎么实现的呢?
- enum是怎么实现线程安全的单例模式?
- enum作为接口定义会出现的扩展问题?
- enum的set和map为什么特殊化?
- 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的反序列流程:
- 首先创建反序列化器,从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;
}
- 接着我们看下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);
}
- 然后我们再看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 域中
- 以上是对 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;
}
}
- 紧接着肯定要看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
- 首先来看EnumMap的根据类型的构造函数:
public EnumMap(Class<K> keyType) {
this.keyType = keyType;
keyUniverse = getKeyUniverse(keyType);
vals = new Object[keyUniverse.length];
}
通过这三个参数也可以看到,首先会约定我们的枚举类型(keyType ),然后会把该枚举的values存起来(keyUniverse ),然后创建了一个存放values的数据vals,这个数组大小和枚举values的长度是一样的,不难想到,最终是要通过下标来做映射关系
- 然后我们看下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);
}
- 紧接着我们看看get方法
public V get(Object key) {
return (isValidKey(key) ?
unmaskNull(vals[((Enum<?>)key).ordinal()]) : null);
}
首先校验key的类型是不是我们限定的枚举类型,然后找到对应下标的vals[]的值
enum的特殊需要注意的地方
- 上面介绍了EnumMap,肯定会对Enum的ordinal感兴趣,接口文档明确表示,这个字段不会被使用,主要的使用地方实在设计师对EnumSet和EnumMap的设计,言外之意如果大家对自己的设计没有想明白的时候,还是不要使用这个字段为好
- Enum类并没有Values() 和 valueOf(String) 方法,这两个方法时编译的时候生成的,这个感觉还是挺烦人的
- 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 序列化,但是却不可以反序列化。
总结
- enum是一种很好的单例模式,也是一种很好的类型列举方式,不过并不是所有的地方都适用
- enum有很多的内容是编译器补充的,比如values()方法和valueOf(String name)方法
- enum尽量在自己系统内部做定义,尽量不要在接口层交互直接对enum类进行序列化和反序列化
网友评论