众所周知,enum类型实例数量是固定的,甚至还被用来设计单例。但有时候仍然存在需要动态增加Enum实例的场景,这也并非一定是设计失败,也可能是增加灵活性的实际需求,比如一些web框架,再比如HanLP 中的动态用户自定义词性。然而最大的障碍是switch语句生成的虚构类,本文参考Java Specialists第161期,提供一份可用的解决方案与实例代码。
一段有问题的代码
比如我们有一个enum类型:
public enum HumanState
{
HAPPY, SAD
}
我们是这样调用的:
public class Human
{
public void sing(HumanState state)
{
switch (state)
{
case HAPPY:
singHappySong();
break;
case SAD:
singDirge();
break;
default:
new IllegalStateException("Invalid State: " + state);
}
}
private void singHappySong()
{
System.out.println("When you're happy and you know it ...");
}
private void singDirge()
{
System.out.println("Don't cry for me Argentina, ...");
}
}
问题在哪里?如果你使用Intelij IDEA的话,你大概会得到一个友好的提示:
不过你可能会说,这个switch分支“永远”不会被触发,就算这句有问题也无伤大雅,甚至这个default分支根本没有存在的必要。
真的吗?
触发不可能的switch分支
Enum类也是类,既然是类,就能通过反射来创建实例,我们创建一个试试。
Constructor cstr = HumanState.class.getDeclaredConstructor(
String.class, int.class
);
ReflectionFactory reflection =
ReflectionFactory.getReflectionFactory();
HumanState e =
(HumanState) reflection.newConstructorAccessor(cstr).newInstance(new Object[]{"ANGRY", 3});
System.out.printf("%s = %d\n", e.toString(), e.ordinal());
Human human = new Human();
human.sing(e);
运行结果
结果出乎意料:
ANGRY = 3
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 3
at com.hankcs.Human.sing(Human.java:21)
at com.hankcs.FireArrayIndexException.main(FireArrayIndexException.java:36)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:483)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:144)
本来指望发生IllegalStateException,怎么出了一个ArrayIndexOutOfBoundsException?
探索问题
虽然我们成功地创建了一个新的Enum实例,但我们却数组越界了。stacktrace指出问题发生在:
switch (state)
这一句,我们不妨看看这一句编译后是什么样子的。借助IDEA的反编译插件,我们可以看到编译后反编译回来的代码:
public class Human {
public Human() {
}
public void sing(HumanState state) {
class Human$1 {
static {
try {
$SwitchMap$com$hankcs$HumanState[HumanState.HAPPY.ordinal()] = 1;
} catch (NoSuchFieldError var2) {
;
}
try {
$SwitchMap$com$hankcs$HumanState[HumanState.SAD.ordinal()] = 2;
} catch (NoSuchFieldError var1) {
;
}
}
}
switch(Human$1.$SwitchMap$com$hankcs$HumanState[state.ordinal()]) {