概述

​ 枚举类就是Java之中一种特殊的类,它具有固定的实例数量,在编译阶段就已经固定,而无法在运行阶段改变的类,并且枚举类往往会只具有一些固定的取值,所以往往很适合充当状态码类型的类。然而因为枚举类的特殊性,所以枚举类除了以上的描述以外还在单例模式的创建下有着重要的作用(最好的单例模式实现方法)

解析

​ 对于枚举类的解析直接看下图代码和执行结果即可。

enum SampleEnum implements Serializable {  // 等同于 class SampleEnum extends enum
    // 枚举类实例 (默认是public static类型的,跟Interface默认自带public abstract类似)
    ENUM_A {
        // 重写成员方法
        public String getVoice () {
            return "special";
        }
        // 这样写的静态方法无法被调用
        public static String getVoice2(){
            return "special2";
        }
    },ENUM_B (12345l), ENUM_C;

    // 有参构造器 ENUM_B 调用
    private SampleEnum (Long x) {
    }

    // 无参构造器 ENUM_A 、 ENUM_C调用  (可以不写)
    private SampleEnum () {
    }

    // 默认成员方法
    public String getVoice() {
        return "Normal";
    }
    // 类方法
    public static String getVoice2(){
        return "Normal2";
    }
}

问题

​ 既然枚举类是固定实例数量的,而枚举类作为一个类势必会有无参的构造方法,虽然构造方法是private的,那能不能通过反射 / 序列化的方式将其反复创建,使其固定实例的特性被破坏掉呢?

反射

try {
    Constructor<SampleEnum> declaredConstructor = SampleEnum.class.getDeclaredConstructor();
    declaredConstructor.setAccessible(true);
    SampleEnum sampleEnum = declaredConstructor.newInstance();
} catch (NoSuchMethodException e) {
    throw new RuntimeException(e);
} catch (InvocationTargetException e) {
    throw new RuntimeException(e);
} catch (InstantiationException e) {
    throw new RuntimeException(e);
} catch (IllegalAccessException e) {
    throw new RuntimeException(e);
}

运行结果

Exception in thread "main" java.lang.RuntimeException: java.lang.NoSuchMethodException: com.leticiafeng.designModel.SampleEnum.<init>()
	at com.leticiafeng.designModel.SampleEnumClass.main(SampleEnumClass.java:16)
Caused by: java.lang.NoSuchMethodException: com.leticiafeng.designModel.SampleEnum.<init>()
	at java.base/java.lang.Class.getConstructor0(Class.java:3585)
	at java.base/java.lang.Class.getDeclaredConstructor(Class.java:2754)
	at com.leticiafeng.designModel.SampleEnumClass.main(SampleEnumClass.java:12)

结果报错报的是,没有找到构造器....这奇怪就奇怪在,我没写有参构造器怎么可能会没有无参构造器呢?然后debug的时候,发现他要求我带上String.class和int.class

image-20240104222947359

​ 那就更怪了,那如果是没有无参构造器,那我加一个总行了吧。然后我就尝试直接在枚举类内部声明一个无参构造器

private SampleEnum () {}

​ 但结果还是一样的。随后我查阅了Enum.class的源码,发现了一个问题,Enum.class没有默认的构造器,那就说明子类构造必须调用super(parameters0, parameters0 ···)来构造。那么很可能是枚举类是封装了一个构造器,在内部通过super(sampleString, sampleInteger)调用Enum.class的构造器来构造的实例对象。而由于enum这个关键字声明的枚举类没有实际上使用extends Enum,这就导致了编译器是无法检查出我们的空构造器是无法构造SampleEnum的。所以实际上我们的无参构造器虽然能通过编译,但是实际运行时,无法被识别为可用的构造器。

==> 枚举类所有构造函数都必须携带 Enum 的构造器的两个参数 String 和 int

So,我们的无参构造器实际效果等同于下面这段代码,而且需要注意的是,即使我们不在枚举类之中写上无参构造器,他还是会默认带上以下形式的构造器。

private SampleEnum (String param1,int param2) {
	super(param1, param2);  //Enum
}

​ 那我们能在枚举类之中添加有参构造器吗?=> 可以,但是在反射之中仍然需要携带用于提供给Enum的两个参数String 和 int。比如

private SampleEnum (Long x) {}  //如果有实例变量构造时,不携带参数,那也需要附加
							 // private SampleEnum () {}
// 实际效果等同于
private SampleEnum (String param1,int param2, Long x) {
	super(param1, param2);  //Enum
    // dosomething by x
    ···
}

image-20240104225118527

而我们在反射之中,如果想要获取 无参构造器 / 有参 对应的构造方法,则需要编写为:

try {
    Class<?> declaringClass = SampleEnum.class.getDeclaringClass();
    Constructor<SampleEnum> declaredConstructor = SampleEnum.class.getDeclaredConstructor(String.class, int.class);   /* 无参 */
    //Constructor<SampleEnum> declaredConstructor = SampleEnum.class.getDeclaredConstructor(String.class, int.class, Long.class);  /* 有参 */
    declaredConstructor.setAccessible(true);
    SampleEnum sampleEnum = declaredConstructor.newInstance();
} catch (NoSuchMethodException e) {
    throw new RuntimeException(e);
} catch (InvocationTargetException e) {
    throw new RuntimeException(e);
} catch (InstantiationException e) {
    throw new RuntimeException(e);
} catch (IllegalAccessException e) {
    throw new RuntimeException(e);
}

结果

java.lang.IllegalArgumentException: Cannot reflectively create enum objects
	at java.base/java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:492)
	at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:480)
	at com.leticiafeng.designModel.SampleEnumClass.main(SampleEnumClass.java:15)

反射还是失败了,报错也很明显直接提示是无法反射枚举对象。那为什么呢?跟刚刚一样的,翻阅源码,很轻松的就能在Constructor.class的newInstance方法找到原因。 (源码是Jdk17,Jdk8的大差不差)

@CallerSensitive
@ForceInline // to ensure Reflection.getCallerClass optimization
public T newInstance(Object ... initargs)
    throws InstantiationException, IllegalAccessException,
           IllegalArgumentException, InvocationTargetException
{
    Class<?> caller = override ? null : Reflection.getCallerClass();
    return newInstanceWithCaller(initargs, !override, caller);
}

/* package-private */
T newInstanceWithCaller(Object[] args, boolean checkAccess, Class<?> caller)
    throws InstantiationException, IllegalAccessException,
           InvocationTargetException
{
    if (checkAccess)
        checkAccess(caller, clazz, clazz, 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(args);
    return inst;
}

​ 关键就在newInstanceWithCaller之中,通过一些较为底层的对枚举类的特殊标记,得出SampleEnum是枚举类,从而直接抛出了无法创建枚举对象的异常信息。

序列化(fastJson1.2.75作为例子)

​ 为了更加简便的进行测试,我是直接使用json来进行序列化和反序列化。

SampleEnum enumA = SampleEnum.ENUM_A;
String s = JSON.toJSONString(enumA);
SampleEnum sampleEnum = JSON.parseObject(s, SampleEnum.class);
System.out.println("Dest SampleEnum:" + sampleEnum);
System.out.println("Source SampleEnum:" + enumA);
System.out.println(sampleEnum.hashCode() + "    " + enumA.hashCode());
Dest SampleEnum:ENUM_A
Source SampleEnum:ENUM_A
343965883    343965883

结果

​ 结果很显然的,虽然序列化和反序列化的过程没有问题,但是实际上对象还是原来的对象,也就是没有破坏掉枚举的唯一性。为什么呢?

原因

toJSONString

toJSONString的序列化调用链很简单,只涉及JSON.class内部的toJSONString的相互调用,而最终负责处理的方法已贴下:

最终传入的Object其实是一个实例对象,实例对象中有Enum的基本属性name和ordinal

public static String toJSONString(Object object, SerializeConfig config, SerializeFilter[] filters, String dateFormat, int defaultFeatures, SerializerFeature... features) {
    SerializeWriter out = new SerializeWriter((Writer)null, defaultFeatures, features);

    String var15;
    try {
        JSONSerializer serializer = new JSONSerializer(out, config);   // 创建Enum的序列化器
        if (dateFormat != null && dateFormat.length() != 0) {
            serializer.setDateFormat(dateFormat);
            serializer.config(SerializerFeature.WriteDateUseDateFormat, true);
        }

        if (filters != null) {
            SerializeFilter[] var8 = filters;
            int var9 = filters.length;

            for(int var10 = 0; var10 < var9; ++var10) {
                SerializeFilter filter = var8[var10];
                serializer.addFilter(filter);
            }
        }

        serializer.write(object);   // 调用Enum序列化器,进行序列化
        var15 = out.toString();
    } finally {
        out.close();
    }

    return var15;
}

image-20240107160209978

而Enum序列化器是哪里产生的呢?

JSON.class - toJSONString() -> JSONSerializer.class - getObjectWriter(Class<?> clazz) -> SerializeConfig.class - getObjectWriter(Class<?> clazz) - getObjectWriter(Class<?> clazz, boolean create)

public ObjectSerializer getObjectWriter(Class<?> clazz, boolean create) {
    ·
    ·
    ·
	if (writer == null) {
        String className = clazz.getName();
        if (Map.class.isAssignableFrom(clazz)) {
        	·
        	·
        	·
        } else {  
            Class mixedInType;
            if (clazz.isEnum()) {   // 不是这个,这个处理的是Enum直接继承类,我们是关键字enum,有点区别
                ·
                ·
                ·
            } else {
                Class superClass;
                if ((superClass = clazz.getSuperclass()) != null && superClass.isEnum()) {
                    JSONType jsonType = (JSONType)TypeUtils.getAnnotation(superClass, JSONType.class);
                    if (jsonType != null && jsonType.serializeEnumAsJavaBean()) {
                        this.put((Type)clazz, (ObjectSerializer)(writer = this.createJavaBeanSerializer(clazz)));
                    } else {
                        this.put((Type)clazz, (ObjectSerializer)(writer = this.getEnumSerializer())); // 这里创建的EnumSerializer对象
                    }
                } else if (clazz.isArray()) {
                    ·
                    ·
                    ·
            }
}

​ 从上面的源码可以知道,是EnumSerializer序列化器,直接找到该类,我们就知道实际write方法是怎么处理的了

public class EnumSerializer implements ObjectSerializer {
    private final Member member;
    public static final EnumSerializer instance = new EnumSerializer();

    public EnumSerializer() {
        this.member = null;
    }

    public EnumSerializer(Member member) {
        this.member = member;
    }

    public void write(JSONSerializer serializer, Object object, Object fieldName, Type fieldType, int features) throws IOException {
        if (this.member == null) {
            SerializeWriter out = serializer.out;    
            out.writeEnum((Enum)object);
        } else {
            Object  = null;

            try {
                if (this.member instanceof Field) {
                    fieldValue = ((Field)this.member).get(object);
                } else {
                    fieldValue = ((Method)this.member).invoke(object);
                }
            } catch (Exception var8) {
                throw new JSONException("getEnumValue error", var8);
            }

            serializer.write(fieldValue);
        }
    }
}

如果member是空(是否有设定序列化的是其他变量,但是在初始化EnumSerializer的时候,没有传任何参数,所以这里恒为null),就会调用SerializeWriter的writeEnum源码,从这里就可以很清晰的看到,他会将实例的name作为序列化的结果进行返回。

public void writeEnum(Enum<?> value) {
    if (value == null) {
        this.writeNull();
    } else {
        String strVal = null;
        if (this.writeEnumUsingName && !this.writeEnumUsingToString) {   // 决定要输出name还是value的toString()
            													    // writeEnumUsingToString该值的设定是toJSONString的一部分参数,所以也是恒为false
            														// 除非调用时进行设定
            strVal = value.name();
        } else if (this.writeEnumUsingToString) {
            strVal = value.toString();
        }

        if (strVal != null) {
            char quote = this.isEnabled(SerializerFeature.UseSingleQuotes) ? 39 : 34;
            this.write(quote);
            this.write(strVal);
            this.write(quote);
        } else {
            this.writeInt(value.ordinal());
        }

    }
}
parseObject

parseJSON实际调用链:

1. DefaultJSONParser.class - parseObject(params) <font color = 'grey'> [设计到重载,这部分就不说了,基本就是填充更多参数来相互调用,最终被调用的是 public <T> T parseObject(Type type, Object fieldName) ] </font> ->  
1. ParserConfig.class - getDeserializer(Type type) -> ParserConfig.class - getDeserializer(Classs<?> clazz, Type type) 获取具体的枚举类对应的ObjectDeserializer。-> 
1. deserializer.deserialze(this, type, fieldName)返回序列化时的实例(通过name)

Part1:ParseObject方法

public <T> T parseObject(Type type, Object fieldName) {
    int token = this.lexer.token();
    if (token == 8) {
        this.lexer.nextToken();
        return null;
    } else {
        if (token == 4) {
            if (type == byte[].class) {
                byte[] bytes = this.lexer.bytesValue();
                this.lexer.nextToken();
                return bytes;
            }

            if (type == char[].class) {
                String strVal = this.lexer.stringVal();
                this.lexer.nextToken();
                return strVal.toCharArray();
            }
        }

        ObjectDeserializer deserializer = this.config.getDeserializer(type); //获取ObjectDeserializer对象

        try {
            if (deserializer.getClass() == JavaBeanDeserializer.class) {
                if (this.lexer.token() != 12 && this.lexer.token() != 14) {
                    throw new JSONException("syntax error,except start with { or [,but actually start with " + this.lexer.tokenName());
                } else {
                    return ((JavaBeanDeserializer)deserializer).deserialze(this, type, fieldName, 0);
                }
            } else {
                return deserializer.deserialze(this, type, fieldName); //最终返回方法
            }
        } catch (JSONException var6) {
            throw var6;
        } catch (Throwable var7) {
            throw new JSONException(var7.getMessage(), var7);
        }
    }
}

Part2:getDeserializer方法

​ ParseConfig下的getDeserializer会先被调用

public ObjectDeserializer getDeserializer(Type type) {
    ObjectDeserializer deserializer = this.get(type);
    if (deserializer != null) {
        return deserializer;
    } else if (type instanceof Class) {
        return this.getDeserializer((Class)type, type);    // 调用同类getDeserializer重载方法
    } else if (type instanceof ParameterizedType) {
	·
	·
	·
}
public ObjectDeserializer getDeserializer(Class<?> clazz, Type type) {
    ·
    ·
    ·
    if (clazz.isEnum()) {
        ·
        ·
        ·
        Method jsonCreatorMethod = null;
        if (mixInType != null) {
            mixedCreator = getEnumCreator(mixInType, clazz);
            if (mixedCreator != null) {
                try {
                    jsonCreatorMethod = clazz.getMethod(mixedCreator.getName(), mixedCreator.getParameterTypes());
                } catch (Exception var12) {
                }
            }
        } else {
            jsonCreatorMethod = getEnumCreator(clazz, clazz);      // 如果存在@JSONCraetor注解方法
        }

        if (jsonCreatorMethod != null) {            // 如果有JSONCreator方法,走Creator方法,返回一个ObjectDeserializer对象
            ObjectDeserializer deserializer = new EnumCreatorDeserializer(jsonCreatorMethod);
            this.putDeserializer(clazz, deserializer);
            return deserializer;
        }

        deserializer = this.getEnumDeserializer(clazz);   // 如果没有JSONCreator方法就走方式2,同样获得一个ObjectDeserializer对象
    } else if (clazz.isArray()) {
        ·
        ·
        ·
	this.putDeserializer((Type)type, (ObjectDeserializer)deserializer);
	return (ObjectDeserializer)deserializer;  // 返回ObjectDeserializer
}
  1. ObjectDeserializer获取方式1:getEnumCreator(clazz,clazz)源码,尝试组装一个枚举类

    ​ getEnumCreator会负责找枚举类之中是否存在着@JSONCreator注解所注解的方法,并尝试通过该方法创建对应的枚举方法。

    image-20240107132456935

    private static Method getEnumCreator(Class clazz, Class enumClass) {
        Method[] methods = clazz.getMethods();
        Method jsonCreatorMethod = null;
        Method[] var4 = methods;
        int var5 = methods.length;
    
        for(int var6 = 0; var6 < var5; ++var6) {
            Method method = var4[var6];
            if (Modifier.isStatic(method.getModifiers()) && method.getReturnType() == enumClass && method.getParameterTypes().length == 1) {  //找到返回类型是enumClass、并且参数的长度是1的静态方法
                JSONCreator jsonCreator = (JSONCreator)method.getAnnotation(JSONCreator.class); // 判断该方法是否存在@JSONCreator注解,如果不存在直接返回null
                if (jsonCreator != null) {
                    jsonCreatorMethod = method;
                    break;
                }
            }
        }
        return jsonCreatorMethod;
    }
    
  2. ObjectDeserializer获取方式2:getEnumDeserializer -> new EnumDeserializer(clazz),利用EnumDeserializer类来帮助组装了一个Enum类 (反射找实例,因为实例都是public static final的,所以肯定是同一个)

    下面是该方法的源码,不建议看,知道这个逻辑就行

    public EnumDeserializer(Class<?> enumClass) {
            this.enumClass = enumClass;
            this.ordinalEnums = (Enum[])((Enum[])enumClass.getEnumConstants());
            Map<Long, Enum> enumMap = new HashMap();
    
            int i;
            for(i = 0; i < this.ordinalEnums.length; ++i) {
                Enum e = this.ordinalEnums[i];
                String name = e.name();
                JSONField jsonField = null;
    
                try {
                    Field field = enumClass.getField(name);
                    jsonField = (JSONField)TypeUtils.getAnnotation(field, JSONField.class);
                    if (jsonField != null) {
                        String jsonFieldName = jsonField.name();
                        if (jsonFieldName != null && jsonFieldName.length() > 0) {
                            name = jsonFieldName;
                        }
                    }
                } catch (Exception var19) {
                }
    
                long hash = -3750763034362895579L;
                long hash_lower = -3750763034362895579L;
    
                int ch;
                for(int j = 0; j < name.length(); ++j) {
                    ch = name.charAt(j);
                    hash ^= (long)ch;
                    hash_lower ^= (long)(ch >= 65 && ch <= 90 ? ch + 32 : ch);
                    hash *= 1099511628211L;
                    hash_lower *= 1099511628211L;
                }
    
                enumMap.put(hash, e);
                if (hash != hash_lower) {
                    enumMap.put(hash_lower, e);
                }
    
                if (jsonField != null) {
                    String[] var25 = jsonField.alternateNames();
                    ch = var25.length;
    
                    for(int var13 = 0; var13 < ch; ++var13) {
                        String alterName = var25[var13];
                        long alterNameHash = -3750763034362895579L;
    
                        for(int j = 0; j < alterName.length(); ++j) {
                            char ch = alterName.charAt(j);
                            alterNameHash ^= (long)ch;
                            alterNameHash *= 1099511628211L;
                        }
    
                        if (alterNameHash != hash && alterNameHash != hash_lower) {
                            enumMap.put(alterNameHash, e);
                        }
                    }
                }
            }
    
            this.enumNameHashCodes = new long[enumMap.size()];
            i = 0;
    
            Long h;
            for(Iterator var20 = enumMap.keySet().iterator(); var20.hasNext(); this.enumNameHashCodes[i++] = h) {
                h = (Long)var20.next();
            }
    
            Arrays.sort(this.enumNameHashCodes);
            this.enums = new Enum[this.enumNameHashCodes.length];
    
            for(i = 0; i < this.enumNameHashCodes.length; ++i) {
                long hash = this.enumNameHashCodes[i];
                Enum e = (Enum)enumMap.get(hash);
                this.enums[i] = e;
            }
    
        }
    

Part3、弹栈回到parseObject方法

​ 由于并非是JavaBeanDeserializer.class,所以走的是else

if (deserializer.getClass() == JavaBeanDeserializer.class) {
    if (this.lexer.token() != 12 && this.lexer.token() != 14) {
        throw new JSONException("syntax error,except start with { or [,but actually start with " + this.lexer.tokenName());
    } else {
        return ((JavaBeanDeserializer)deserializer).deserialze(this, type, fieldName, 0);
    }
} else {
    return deserializer.deserialze(this, type, fieldName);  // EnumDeserializer的deserialze
}

​ parseObject调用的EnumDeserializer方法就更加简单了,本质上其实就是根据序列化的name,产生对应的hashCode,找到对应的实例并返回而已,但是代码比较长,已经贴在下面了。

public <T> T deserialze(DefaultJSONParser parser, Type type, Object fieldName) {
    try {
        JSONLexer lexer = parser.lexer;
        int token = lexer.token();
        if (token == 2) {
            int intValue = lexer.intValue();
            lexer.nextToken(16);
            if (intValue >= 0 && intValue < this.ordinalEnums.length) {
                return this.ordinalEnums[intValue];
            } else {
                throw new JSONException("parse enum " + this.enumClass.getName() + " error, value : " + intValue);
            }
        } else if (token != 4) {
            Object value;
            if (token == 8) {
                value = null;
                lexer.nextToken(16);
                return null;
            } else {
                value = parser.parse();
                throw new JSONException("parse enum " + this.enumClass.getName() + " error, value : " + value);
            }
        } else {
            String name = lexer.stringVal();
            lexer.nextToken(16);
            if (name.length() == 0) {
                return null;
            } else {
                long hash = -3750763034362895579L;
                long hash_lower = -3750763034362895579L;

                for(int j = 0; j < name.length(); ++j) {
                    char ch = name.charAt(j);
                    hash ^= (long)ch;
                    hash_lower ^= (long)(ch >= 'A' && ch <= 'Z' ? ch + 32 : ch);
                    hash *= 1099511628211L;
                    hash_lower *= 1099511628211L;
                }

                Enum e = this.getEnumByHashCode(hash);
                if (e == null && hash_lower != hash) {
                    e = this.getEnumByHashCode(hash_lower);
                }

                if (e == null && lexer.isEnabled(Feature.ErrorOnEnumNotMatch)) {
                    throw new JSONException("not match enum value, " + this.enumClass.getName() + " : " + name);
                } else {
                    return e;
                }
            }
        }
    } catch (JSONException var14) {
        throw var14;
    } catch (Exception var15) {
        throw new JSONException(var15.getMessage(), var15);
    }
}

总结

​ 反序列化的工作到这里已经基本结束了,后续parseObject的处理代码都对枚举类是不做任何操作的。所以可以得出结论。实际上枚举类的序列化过程只把当前序列目标的name保存下来。而反序列化的时候则是先构建枚举类的ObjectDeserializer对象,利用了枚举类实例的public static final属性,反射获取实例的时候保证获取的是同一个,然后同name来找到对应的实例并返回。这因此这个过程不会涉及到新枚举类对象的创建,所以保证了实例的唯一性。至于普通对象的序列化和反序列过程可以参考其他文章。