所有的枚举类型都是由编译器通过继承 Enum 类来创建的。然而,如果仔细查看Enum类的代码,你会发现里面并没有 values() 方法,我们却已经能直接使用它了,是哪里有其他“隐藏”的方法吗?我们可以编写一个小的反射程序来一探究竟:
Reflection.java
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.Set;
import java.util.TreeSet;
enum Explore {HERE, THERE}
public class Reflection {
public static Set<String> analyze(Class<?> enumClass) {
System.out.println("_____ Analyzing " + enumClass + " _____");
System.out.println("Interfaces:");
for (Type t : enumClass.getGenericInterfaces()) {
System.out.println(t);
}
System.out.println("Base: " + enumClass.getSuperclass());
System.out.println("Methods: ");
Set<String> methods = new TreeSet<>();
for (Method m : enumClass.getMethods()) {
methods.add(m.getName());
}
System.out.println(methods);
return methods;
}
public static void main(String[] args) {
Set<String> exploreMethods =
analyze(Explore.class);
Set<String> enumMethods = analyze(Enum.class);
System.out.println("Explore.containsAll(Enum)? " + exploreMethods.containsAll(enumMethods));
System.out.print("Explore.removeAll(Enum): ");
exploreMethods.removeAll(enumMethods);
System.out.println(exploreMethods);
// Explore.class 的位置
OSExecute.command("javap -cp out/production/myTest/enums/TEST0515 Explore");
}
}
OSExecute.java
import java.io.*;
public class OSExecute {
public static void command(String command) {
boolean err = false;
try {
Process process = new ProcessBuilder(command.split(" ")).start();
try (
BufferedReader results = new BufferedReader(new InputStreamReader(process.getInputStream()));
BufferedReader errors = new BufferedReader(new InputStreamReader(process.getErrorStream()))
) {
results.lines().forEach(System.out::println);
err = errors.lines().peek(System.err::println).count() > 0;
}
} catch (IOException e) {
throw new RuntimeException(e);
}
if (err) {
// 直接抛出异常可能导致以上内容打印不出来
// throw new OSExecuteException("Errors executing " + command);
System.err.println("Errors executing " + command);
}
}
}
运行结果如下:
答案揭晓,values() 方法是由编译器添加的一个静态方法。注意在创建枚举的过程中,value0f() 方法同样也被添加到了 Explore 枚举中。这有点让人糊涂,Enum 类中同样也有一个 value0f() 方法,但是该方法有2个参数,而新加入的方法则只有 1 个。然而,这里的Set方法只关心方法名,并不关心方法签名,所以在调用 Explore.removeAll(Enum) 后,只剩下了 [values]。
打印结果显示 Explore 枚举被编译器限定为 final 类,所以你无法继承一个枚举类。此外还有一个static的初始化子句,你稍后会看到它可以被重定义。
由于类型擦除(相关介绍参见基础卷第20章)的缘故,反编译器得不到 Enum 类的完整信息,因此只能将 Explore 类的基类作为一个原始的 Enum 类来显示,而不是实际上的 Enum。
由于 values() 方法是由编译器在枚举类的定义中插入的一个静态方法,因此如果你将枚举类型向上转型为 Enum,则 values() 方法将不可用。然而要注意的是,Class 中有个 getEnunConstants() 方法,所以即使 Enum 的接口中没有 values() 方法,仍然可以通过 Class 对象来得到 enum 的实例:
UpcastEnum.java
enum Search {HITHER, YON}
public class UpcastEnum {
public static void main(String[] args) {
Search[] vals = Search.values();
Enum e = Search.HITHER; // Upcast
// e.values(); // No values() in Enum
for (Enum en : e.getClass().getEnumConstants()) {
System.out.println(en);
}
}
}
运行结果如下:
HITHER
YON
由于 getEnumconstants() 是Class类中的一个方法,因此对一个没有枚举的类也可以调用该方法。
NonEnum.java
public class NonEnum {
public static void main(String[] args) {
Class<Integer> intClass = Integer.class;
try {
for (Object en : intClass.getEnumConstants()) {
System.out.println(en);
}
} catch (Exception e) {
System.out.println("Expected: " + e);
}
}
}
运行结果如下:
Expected: java.lang.NullPointerException
该方法会返回null,因此如果你尝试引用该结果,就会抛出异常。