Enum深入解析
enum 关键字和 java.lang.Enum 的关系
enum 是编译器的语法糖,这一点可以用 javap 反编译验证
我们使用 enum 关键字定义了一个枚举,经过 javac 编译,编译器会对其进行加工,具体如下:
-
类的签名:final class Demo extends java.lang.Enum<Demo>
-
枚举值
public static final Demo A; // 枚举实例
-
枚举数组
private static final Demo[] $VALUES; // 通过反汇编可以知道,在静态代码块中初始化枚举值的同时,并放入枚举数组中
-
静态代码块
static {} // 反汇编得知:1. new 对象 2. 初始化枚举值 3. 初始化枚举数组
-
values 方法
public static Demo[] values(); // 反汇编得知:1. 对枚举数组调用虚方法 clone() 2. 类型转换 3. 返回
-
valueOf(String name) 方法
public static Demo valueOf(java.lang.String); // 里面调用父类的 valueOf(Class enumType, String name)实现
-
如果没有私有构造器,则添加私有构造器
private Demo (java.lang.String, int); // 调用父类唯一构造器实现
-
如果有私有构造器,则添加新的私有构造器
如果原来构造器为 private Demo(java.lang.String), 则新构造器为 private Demo(java.lang.String, int, java.lang.String)
Enum解析
类签名
public abstract class Enum<E extends Enum<E>>
implements Comparable<E>, Serializable
实现了 Comparable 接口,说明相同类型的枚举对象可以比较(实际是比较 ordinal 大小,也就是定义的顺序)
实现了Serializable,说明可以被序列化
属性
/**
* 枚举常量的字符串名称
*/
private final String name;
/**
* 枚举常量在定义时的顺序,用于基于枚举的复杂数据结构,如 java.util.EnumSet、java.util.EnumMap
*/
private final int ordinal;
两个属性有对应的访问方法 name() 和 ordinal(),一般建议用 toString() 方法来访问 name 属性,因为 toString 方法的默认实现是返回 name 值,而且可以被覆盖
方法
public final boolean equals(Object other) {
return this==other; // 默认情况下调用 equal 和 == 是相同的
}
protected final Object clone() throws CloneNotSupportedException {
throw new CloneNotSupportedException(); // 对象不可以克隆,保证全局唯一(单例状态)
}
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);
}
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"); // 默认不支持序列化
}
valueOf 方法调用了 Class 的 enumConstantDirectory() 方法获取对象,然后进行校验,返回
enumConstantDirectory() 的源码:
private volatile transient Map<String, T> enumConstantDirectory = null;
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 T[] enumConstants = null;
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;
}
enumConstantDirectory() 方法通过懒加载的方式获取类下的枚举数组,再根据(枚举名称、枚举实例)放入Map;
在获取类下的枚举数组时,通过懒加载方式,反射 Enum 中的 values() 方法,获取
拓展
通过源码得知 enum 的 valueOf 方法是调用 Enum 的 valueOf 方法实现,Enum 的 valueOf 方法实现中反射调用 enum 的 values 方法;enum 中的 values 方法是 javac 帮助生成的;那么我们自己编写一个类继承 Enum,而其中不写 values 方法,在调用 valueOf 方法时会怎样?理论上来说会抛出NoSuchMethodException,但是实际上,当你这么做时,你会发现 IDE 给你报了个错:Classes cannot directly extend ‘java.lang.Enum’
总结
- 枚举是单例模式的最佳实践,在考虑单例模式设计的时候,请优先考虑枚举
- 在 values 方法中调用了 clone 方法,保证枚举数组不会被篡改、同时也增加了内存的开销