枚举类型需要重写hashcode()吗?

需要重写equals()方法的类,也需要重写hashcode(),不然在使用HashMap或者HashSet类时,会出现不正确的结果。
那么什么时候需要equals()方法呢?
Object类的equals()实现为比较对象的引用地址,所以,不想比较对象引用,而比较其他情况时,需要重写equals()方法。
大多情况下,我们需要比较对象的变量是否相等,此时,这种类型被称为“值类型”。
对于枚举类型,如果我们需要比较其变量相等情况时,看看是否需要重写equals()和hashcode()?

语法糖

枚举类型enum为Java语言的一个语法糖,可以使用反编译工具,看一下Java编译器是否如何实现enum的。
源代码:

1
2
3
4
enum Sex{
MALE,
FAMALE
}

反编译的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
static final class JavaSugar.Sex
extends Enum<JavaSugar.Sex> {
public static final /* enum */ JavaSugar.Sex MALE = new JavaSugar.Sex((String)"MALE", (int)0);
public static final /* enum */ JavaSugar.Sex FAMALE = new JavaSugar.Sex((String)"FAMALE", (int)1);
private static final /* synthetic */ JavaSugar.Sex[] $VALUES;

public static JavaSugar.Sex[] values() {
return (JavaSugar.Sex[])$VALUES.clone();
}

public static JavaSugar.Sex valueOf(String name) {
return Enum.valueOf(JavaSugar.Sex.class, (String)name);
}

private JavaSugar.Sex(String string, int n) {
super((String)string, (int)n);
}

static {
$VALUES = new JavaSugar.Sex[]{MALE, FAMALE};
}
}

通过反编译后的代码,我们可以看到:

  • 构造函数为私有的,所有的枚举值都是静态创建。这属于懒汉式的单例模式,并且创建过程是线程安全的。
  • 类型和所有的枚举值都由final来修饰,并且所有方法对于变量操作都是只读的。说明这是一个不可变类型。

枚举同时具备单例模式和不可变类型,这样整个Java应用中的枚举类型实例就只有定义的枚举值(MALE和FAMALE)。
这样,不同枚举值的变量(name)理论上是不会出现相等情况的(除非定义枚举值使用相同的变量值,但这个枚举的意义是相悖的)。引用不同的枚举对象,其枚举变量肯定是不同的。
所以,结果是: 枚举类型不需要重写equals()方法和hashcode()方法

枚举类型的反序列化

枚举类型是否还有其他方式创建枚举值?我不能确定反序列化是否有新的方式来创建枚举值。
通过阅读反序列化的源代码,可以窥探一二。
默认情况下,序列化枚举实例,得到的结果为枚举的name变量值。
Enum类,提供了valueOf(Class<T> enumType,String name)方法,可以使用name值得到枚举实例。
其源代码:

1
2
3
4
5
6
7
8
9
10
public static <T extends Enum<T>> T valueOf(Class<T> enumType,
String name) {
T result = enumType.enumConstantDirectory().get(name);
if (result != null)
return result;
if (name == null)
throw new NullPointerException("Name is null");
throw new IllegalArgumentException(
"No enum constant " + enumType.getCanonicalName() + "." + name);
}

再看一下enumConstantDirectory()方法:

  • enumConstantDirectory为一个name->Enum Type的Map
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    Map<String, T> enumConstantDirectory() {
    if (enumConstantDirectory == null) {
    T[] universe = getEnumConstantsShared();
    if (universe == null)
    throw new IllegalArgumentException(
    getName() + " is not an enum type");
    Map<String, T> m = new HashMap<>(2 * universe.length);
    for (T constant : universe)
    m.put(((Enum<?>)constant).name(), constant);
    enumConstantDirectory = m;
    }
    return enumConstantDirectory;
    }
    private volatile transient Map<String, T> enumConstantDirectory = null;

再看一下getEnumConstantsShared()方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
T[] getEnumConstantsShared() {
if (enumConstants == null) {
if (!isEnum()) return null;
try {
final Method values = getMethod("values");
java.security.AccessController.doPrivileged(
new java.security.PrivilegedAction<Void>() {
public Void run() {
values.setAccessible(true);
return null;
}
});
@SuppressWarnings("unchecked")
T[] temporaryConstants = (T[])values.invoke(null);
enumConstants = temporaryConstants;
}
// These can happen when users concoct enum-like classes
// that don't comply with the enum spec.
catch (InvocationTargetException | NoSuchMethodException |
IllegalAccessException ex) { return null; }
}
return enumConstants;
}

上述getMethod("values")方法,应该就是下面的方法。这样的话,valueOf(Class<T> enumType,String name)方法返回的枚举实例,并没有创建新的实例。

1
2
3
public static JavaSugar.Sex[] values() {
return (JavaSugar.Sex[])$VALUES.clone();
}

反序列化,有很多方式,这里看一下ObjectInputStreamGson两种方式:

ObjectInputStream

其核心代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
private Enum<?> readEnum(boolean unshared) throws IOException {
if (bin.readByte() != TC_ENUM) {
throw new InternalError();
}

ObjectStreamClass desc = readClassDesc(false);
if (!desc.isEnum()) {
throw new InvalidClassException("non-enum class: " + desc);
}

int enumHandle = handles.assign(unshared ? unsharedMarker : null);
ClassNotFoundException resolveEx = desc.getResolveException();
if (resolveEx != null) {
handles.markException(enumHandle, resolveEx);
}

String name = readString(false);
Enum<?> result = null;
Class<?> cl = desc.forClass();
if (cl != null) {
try {
@SuppressWarnings("unchecked")
Enum<?> en = Enum.valueOf((Class)cl, name);
result = en;
} catch (IllegalArgumentException ex) {
throw (IOException) new InvalidObjectException(
"enum constant " + name + " does not exist in " +
cl).initCause(ex);
}
if (!unshared) {
handles.setObject(enumHandle, result);
}
}

handles.finish(enumHandle);
passHandle = enumHandle;
return result;
}

其创建实例的代码:

1
2

Enum<?> en = Enum.valueOf((Class)cl, name);

所以,其不会创建新的枚举实例。

Gson

核心代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
private static final class EnumTypeAdapter<T extends Enum<T>> extends TypeAdapter<T> {
public EnumTypeAdapter(Class<T> classOfT) {
try {
for (T constant : classOfT.getEnumConstants()) {
String name = constant.name();
SerializedName annotation = classOfT.getField(name).getAnnotation(SerializedName.class);
if (annotation != null) {
name = annotation.value();
for (String alternate : annotation.alternate()) {
nameToConstant.put(alternate, constant);
}
}
nameToConstant.put(name, constant);
constantToName.put(constant, name);
}
} catch (NoSuchFieldException e) {
throw new AssertionError(e);
}
}
}

classOfT.getEnumConstants()方法的源代码如下

1
2
3
4
public T[] getEnumConstants() {
T[] values = getEnumConstantsShared();
return (values != null) ? values.clone() : null;
}

这个getEnumConstantsShared()方法,我们上面已经看过了。
这样,Gson反序列化,也不会产生新的实例。

总结

反序列化,并不会创建新的枚举实例。