一、分析自定义枚举类
普通的枚举类和抽象枚举类相似,故直接分析抽象枚举类。
1. 编写一个抽象枚举类
package org.example;
public enum Operator {
ADD ("+"){
@Override
public int calculate(int a, int b) {
return a + b;
}
},
SUB ("-"){
@Override
public int calculate(int a, int b) {
return a - b;
}
},
MUL ("*"){
@Override
public int calculate(int a, int b) {
return a * b;
}
},
DIV ("/"){
@Override
public int calculate(int a, int b) {
return a / b;
}
};
private String identifier;
Operator(String identifier){
this.identifier = identifier;
}
public abstract int calculate(int a, int b);
public String getIdentifier(){
return identifier;
}
}
2. 编译
使用命令
javac Operator
将.java
文件编译成.class
文件。
编译生成如下五个文件:
Operator.class
Operator$1.class
Operator$2.class
Operator$3.class
Operator$4.class
Operator.class
是Operator
的编译结果,但是其余四个.class
文件目前不明确来源。
3. 反编译
普通的枚举类的反编译结果使用
final
进行修饰。
3.1 查看Operator
简单内容
通过命令
javap Operator
反编译Operator.class
。
反编译结果:
public abstract class org.example.Operator extends java.lang.Enum {
public static final org.example.Operator ADD;
public static final org.example.Operator SUB;
public static final org.example.Operator MUL;
public static final org.example.Operator DIV;
public static org.example.Operator[] values();
public static org.example.Operator valueOf(java.lang.String);
public abstract int calculate(int, int);
public java.lang.String getIdentifier();
org.example.Operator(java.lang.String, int, java.lang.String, org.example.Operator$1);
static {};
}
反编译之后的结果多了values
、valueOf
以及一个static
代码块,而且构造函数的参数和我们写的参数也不一致。但是有用信息太少,需要查看详细的编译信息,用来确定这些新增代码的用途。
3.2 查看Operator
详细信息
通过命令
javap -c -v Operator
查看Operator.class
反编译的详细信息。
具体的信息由于篇幅原因不展示具体的编译信息,只展示如下分析结果:
public abstract class org.example.Operator extends java.lang.Enum {
public static final org.example.Operator ADD;
public static final org.example.Operator SUB;
public static final org.example.Operator MUL;
public static final org.example.Operator DIV;
private static final org.example.Operator[] $VALUES;
private String identifier;
// 抽象方法,在子类中实例化
public abstract int calculate(int, int);
public java.lang.String getIdentifier(){
return identifier;
}
// 关于编译之后添加的name、ordinal可以在Enum中看出它们的用途
private org.example.Operator(String name, int ordinal, String identifier){
super(name, ordinal);
this.identifier = identifier;
}
// 看不懂该方法的作用
org.example.Operator(String name, int ordinal, String identifier, org.example.Operator$1 ){
this(name, ordinal, identifier);
}
public static org.example.Operator[] values(){
return (Operator[])$VALUES.clone();
}
// 根据名字返回对应的枚举常量,具体看Eunm类的分析
public static org.example.Operator valueOf(String name){
return (Operator)super.valueOf(Operator.class, name);
}
// 实例化枚举类型中定义的常量
static {
ADD = new Operator$1("ADD", 0, "+");
SUB = new Operator$2("SUB", 1, "-");
MUL = new Operator$3("MUL", 2, "*");
DIV = new Operator$4("DIV", 3, "/");
$VALUES = new Operator[4];
$VALUES[0] = ADD;
$VALUES[1] = SUB;
$VALUES[2] = MUL;
$VALUES[3] = DIV;
};
}
InnerClasses:
static #24; //class org/example/Operator$4
static #19; //class org/example/Operator$3
static #14; //class org/example/Operator$2
static #9; //class org/example/Operator$1
最后的InnerClasses
部分解答了之前的疑惑:每一个枚举对象就是一个匿名内部类的实例。该实例在Operator
类的static
代码块中初始化。并且每个枚举类型内部都会维护一个$VALUES
数组,用来保存内部所有的枚举实例。
3.3 查看Operator$1.class
详细信息
使用
javap -c -v Operator$1
查看Operator$1.class
反编译的详细信息。
其余的几个和Operator$1.class
类似,不做多余解释。
同样只展示分析后的结果:
final class org.example.Operator$1 extends org.example.Operator{
// 构造函数传入了一个匿名子类的实例(实际上是`null`),搞不懂
org.example.Operator$1(String name, int ordinal, String identifier){
super(name, ordinal, identifier, null);
}
public int calculate(int a, int b){
return a + b;
}
}
很简单的匿名内部类的实现,单纯的实现了父类的抽象方法。
自定义枚举分析完毕,下来看一下Enum
类。
二、分析Enum
类源码
public abstract class Enum> implements Comparable, Serializable {
/**
* 枚举常量的名字,在本案例中是“ADD”、“SUB”、“MUL”、“DIV”。
* 大多数的程序应该在{@link #toString}中引用该字段,而不是直接访问该字段。
* 因此{@link #toString}需要返回对用户友好的字符串。
*/
private final String name;
public final String name() { return name; }
/**
* 枚举常量的定义的顺序(第一个常量的ordinal从0开始)。
* 大多数的程序中不应该使用该字段。该字段被一些基于枚举的数据结构所使用,例如
* {@link java.util.EnumSet} and {@link java.util.EnumMap}.
*/
private final int ordinal;
public final int ordinal() { return ordinal; }
/**
* 唯一的构造方法。程序不能执行该构造方法。它只能被编译器编生成的代码
* (ACC_SYNTHETIC,比如{@link org.example.Operator(String, int, String)})
* 所使用。
*
* 如果要是通过反射调用该构造方法,也会抛出异常。因为在反射API中对Class的类型做了判断。
*/
protected Enum(String name, int ordinal) {
this.name = name;
this.ordinal = ordinal;
}
/**
* 该方法应该被重写,并返回一些对用户友好的字符串。
*/
public String toString() { return name; }
public final boolean equals(Object other) { return this==other; }
public final int hashCode() { return super.hashCode(); }
/** enum classes cannot have finalize methods. */
protected final void finalize() { }
/**
* 该函数是通过ordinal字段来比较的,因此其返回结果和常量定义的顺序有关系。
* 并且枚举常量只能和相同类型的枚举常量进行比较。
*/
public final int compareTo(E o) {
Enum> other = (Enum>)o;
Enum self = this;
if (self.getClass() != other.getClass() && // optimization
self.getDeclaringClass() != other.getDeclaringClass())
throw new ClassCastException();
return self.ordinal - other.ordinal;
}
/**
* 返回该枚举类型正确的Class对象。
* 在当前案例中,每一个枚举的Class对象应该是内部类,比如“ADD”对象实际上是Class,
* 但这些是虚拟机内部的实现,用户想要看到的是Class。
*/
@SuppressWarnings("unchecked")
public final Class getDeclaringClass() {
Class> clazz = getClass();
Class> zuper = clazz.getSuperclass();
return (zuper == Enum.class) ? (Class)clazz : (Class)zuper;
}
/**
* 返回指定枚举类型中指定名字的枚举常量。该name在每个枚举类型中唯一。
*
* 该方法被每个枚举类型中隐式的{@code public static T valueOf(String)}
* 所使用。程序应该使用每个枚举类型中的{@code public static T valueOf(String)}。
*/
public static > T valueOf(Class enumType, String name) {
// 根据name从map中取该常量
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);
}
/**
* 该方法抛出一个异常,为了保证枚举常量“单例”的特性。
*/
protected final Object clone() throws CloneNotSupportedException {
throw new CloneNotSupportedException();
}
/**
* 防止反序列化攻击,为了保证枚举常量“单例”的特性。
*/
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");
}
}
三、收获
枚举如何保证单例性
-
new
关键字:- 枚举:直接在编译器层面进行了控制。
- 替代:可以通过构造方法私有化来实现。
- 继承:
- 枚举:在编译器层面做了控制,无法继承一个枚举类。
- 替代:普通类可以通过
final
关键字实现。抽象类则无法实现,因为我们没有办法控制一个类只被特定的几个类继承,但是可以通过其他方法间接实现该效果。
- 反射API:
- 枚举:在反射API中进行了控制。
- 替代:可以在单例对象的构造方法中进行判断来实现。
- 反序列化:
- 枚举:在反序列化用到的方法中进行了控制。
- 替代:可以通过重写反序列化方法实现。
关于反射API
反射API一般通过Class.newInstance
来获取实例对象:
// java.lang.Class
@CallerSensitive
public T newInstance() throws InstantiationException, IllegalAccessException {
// ... 省略其他代码
try {
return tmpConstructor.newInstance((Object[])null);
} catch (InvocationTargetException e) {
Unsafe.getUnsafe().throwException(e.getTargetException());
return null;
}
}
该方法通过 tmpConstructor.newInstance((Object[])null);
新建对象。继续跟踪:
// java.lang.reflect.Constructor
@CallerSensitive
public T newInstance(Object ... initargs) throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
// ... 省略其他代码
if ((clazz.getModifiers() & Modifier.ENUM) != 0)
throw new IllegalArgumentException("Cannot reflectively create enum objects");
// ... 省略其他代码
}
可以看到反射API对Enum
类型做了判断,不会实例化Enum
类型。