java
枚举常量的精确类型一定是当前枚举类型吗?
枚举常量的精确类型是当前枚举类型的例子
有的朋友可能会说,枚举常量的精确类型当然是当前枚举类型。随便写段代码就能验证 ⬇️
java
public enum Direction {
EAST, WEST, SOUTH, NORTH;
public static void main(String[] args) {
for (var direction : Direction.values()) {
System.out.println(direction.getClass().getName());
}
}
}
请将上面的代码保存为 Direction.java
。用如下命令可以编译 Direction.java
并运行其中的 main(...)
方法。
bash
javac Direction.java && java Direction
运行结果为 ⬇️
text
Direction
Direction
Direction
Direction
可见 EAST, WEST, SOUTH, NORTH
这 <math xmlns="http://www.w3.org/1998/Math/MathML"> 4 4 </math>4 个枚举常量的精确类型都是 Direction
。 运行 javap -p Direction
命令可以看到 Direction.class
文件的简要内容 ⬇️
java
Compiled from "Direction.java"
public final class Direction extends java.lang.Enum<Direction> {
public static final Direction EAST;
public static final Direction WEST;
public static final Direction SOUTH;
public static final Direction NORTH;
private static final Direction[] $VALUES;
public static Direction[] values();
public static Direction valueOf(java.lang.String);
private Direction();
public static void main(java.lang.String[]);
private static Direction[] $values();
static {};
}
从中可以看出,EAST, WEST, SOUTH, NORTH
是 Direction
类的 <math xmlns="http://www.w3.org/1998/Math/MathML"> 4 4 </math>4 个实例,所以这些枚举常量的精确类型是当前枚举类型(即 Direction
类)。
但是,这样并不能说明没有反例存在。
枚举常量的精确类型不是当前枚举类型的例子
Java Language Specification 中的 8.9.3. Enum Members 小节 里有这样的内容 ⬇️

我把这里的 Operation
枚举类做了些调整 ⬇️
java
public enum Operation {
PLUS {
double eval(double x, double y) { return x + y; }
},
MINUS {
double eval(double x, double y) { return x - y; }
},
TIMES {
double eval(double x, double y) { return x * y; }
},
DIVIDED_BY {
double eval(double x, double y) { return x / y; }
};
// Each constant supports an arithmetic operation
abstract double eval(double x, double y);
public static void main(String[] args) {
System.out.println("The name of Operation.class is: " + Operation.class.getName());
System.out.println();
for (var operation : Operation.values()) {
System.out.println("The name of " + operation.name() + ".getClass() is: " + operation.getClass().getName());
}
}
}
请将上方的代码保存为 Operation.java
。 用如下命令可以编译 Operation.java
并运行其中的 main(...)
方法。
bash
javac Operation.java && java Operation
运行结果如下 ⬇️
text
The name of Operation.class is: Operation
The name of PLUS.getClass() is: Operation$1
The name of MINUS.getClass() is: Operation$2
The name of TIMES.getClass() is: Operation$3
The name of DIVIDED_BY.getClass() is: Operation$4
在这个例子中,枚举常量的精确类型分别是 Operation$1
/Operation$2
/Operation$3
/Operation$4
。这都是什么?别急,我们来看看有哪些 class
文件。在编译之后,当前目录下出现了 <math xmlns="http://www.w3.org/1998/Math/MathML"> 5 5 </math>5 个 class
文件 ⬇️
Operation.class
Operation$1.class
Operation$2.class
Operation$3.class
Operation$4.class
如果我们执行 javap -v -p Operation
命令,会看到 Operation.class
的具体内容。但是字节码的内容读起来有点费劲,而且也不是本文的重点,所以我们借助 Intellij IDEA
来看看 Operation.class
反编译后的结果吧 ⬇️ (这里展示的结果并不完整,但截图之外的部分本文用不到)

从反编译的结果可以看出,PLUS, MINUS, TIMES, DIVIDED_BY
分别是 Operation
的某个匿名内部类的实例。
枚举常量 | 精确类型 | 说明 |
---|---|---|
Operation.PLUS |
Operation$1 |
Operation$1 是 Operation 的一个匿名内部类 |
Operation.MINUS |
Operation$2 |
Operation$2 是 Operation 的一个匿名内部类 |
Operation.TIMES |
Operation$3 |
Operation$3 是 Operation 的一个匿名内部类 |
Operation.DIVIDED_BY |
Operation$4 |
Operation$4 是 Operation 的一个匿名内部类 |
对应的类图如下 ⬇️ (有些字段/方法省略了)
相关的方法
是否有方法可以同时支持以下两种情况呢?⬇️
- 通过
Direction.EAST
得到Direction
类 - 通过
Operation.PLUS
得到Operation
类
在 Enum.java 里可以找到这个方法 ⬇️
java
/**
* Returns the Class object corresponding to this enum constant's
* enum type. Two enum constants e1 and e2 are of the
* same enum type if and only if
* e1.getDeclaringClass() == e2.getDeclaringClass().
* (The value returned by this method may differ from the one returned
* by the {@link Object#getClass} method for enum constants with
* constant-specific class bodies.)
*
* @return the Class object corresponding to this enum constant's
* enum type
*/
@SuppressWarnings("unchecked")
public final Class<E> getDeclaringClass() {
Class<?> clazz = getClass();
Class<?> zuper = clazz.getSuperclass();
return (zuper == Enum.class) ? (Class<E>)clazz : (Class<E>)zuper;
}
从代码中可以看出,这个方法可以覆盖本文提到的两种情况。这个方法返回的值一定是当前枚举类型。