java 修改 枚举类字段
在本新闻通讯中,该新闻通讯最初发表在Java专家的新闻通讯第161期中,我们研究了如何使用sun.reflect包中的反射类在Sun JDK中创建枚举实例。 显然,这仅适用于Sun的JDK。 如果需要在另一个JVM上执行此操作,则您可以自己完成。
所有这些都始于爱丁堡的Ken Dobson的电子邮件,该电子邮件向我指出了sun.reflect.ConstructorAccessor
的方向,他声称可以将其用于构造枚举实例。 我以前的方法(通讯#141)在Java 6中不起作用。
我很好奇为什么Ken要构造枚举。 这是他想使用它的方式:
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, ...");
}
}
上面的代码需要进行单元测试。 你发现错误了吗? 如果没有,请使用细梳再次遍历代码以尝试找到它。 当我第一次看到这个时,我也没有发现错误。
当我们产生这样的错误时,我们应该做的第一件事就是进行一个显示它的单元测试。 但是,在这种情况下,我们无法使default
情况发生,因为HumanState仅具有HAPPY和SAD枚举。
Ken的发现使我们可以使用sun.reflect包中的ConstructorAccessor类来创建枚举的实例。 它涉及到以下内容:
Constructor cstr = clazz.getDeclaredConstructor(
String.class, int.class
);
ReflectionFactory reflection =
ReflectionFactory.getReflectionFactory();
Enum e =
reflection.newConstructorAccessor(cstr).newInstance("BLA",3);
但是,如果仅执行此操作,则最终会出现ArrayIndexOutOfBoundsException,这在我们看到Java编译器如何将switch语句转换为字节代码时才有意义。 以上面的Human类为例,下面是反编译后的代码(感谢Pavel Kouznetsov的JAD ):
public class Human {
public void sing(HumanState state) {
static class _cls1 {
static final int $SwitchMap$HumanState[] =
new int[HumanState.values().length];
static {
try {
$SwitchMap$HumanState[HumanState.HAPPY.ordinal()] = 1;
} catch(NoSuchFieldError ex) { }
try {
$SwitchMap$HumanState[HumanState.SAD.ordinal()] = 2;
} catch(NoSuchFieldError ex) { }
}
}
switch(_cls1.$SwitchMap$HumanState[state.ordinal()]) {
case 1:
singHappySong();
break;
case 2:
singDirge();
break;
default:
new IllegalStateException("Invalid State: " + state);
break;
}
}
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, ...");
}
}
您可以立即看到为什么要得到ArrayIndexOutOfBoundsException,这要归功于内部类_cls1。
我第一次尝试解决此问题并没有得到一个不错的解决方案。 我试图在HumanState枚举中修改$ VALUES数组。 但是,我只是摆脱了Java的保护性代码。 您可以修改final字段 ,只要它们是非静态的即可。 这种限制对我来说似乎是人为的,因此我着手寻找静态最终领域的圣杯。 再次,它被隐藏在阳光反射的房间里。
设置“最终静态”字段
设置final static
字段需要几件事。 首先,我们需要使用法线反射获取Field对象。 如果将其传递给FieldAccessor,我们将退出安全代码,因为我们正在处理静态的final字段。 其次,我们将Field对象实例内的修饰符字段值更改为非最终值。 第三,我们将经过修改的字段传递给sun.reflect包中的FieldAccessor并使用它进行设置。
这是我的ReflectionHelper类,可用于通过反射设置final static
字段:
import sun.reflect.*;
import java.lang.reflect.*;
public class ReflectionHelper {
private static final String MODIFIERS_FIELD = "modifiers";
private static final ReflectionFactory reflection =
ReflectionFactory.getReflectionFactory();
public static void setStaticFinalField(
Field field, Object value)
throws NoSuchFieldException, IllegalAccessException {
// we mark the field to be public
field.setAccessible(true);
// next we change the modifier in the Field instance to
// not be final anymore, thus tricking reflection into
// le