是否可以在运行时以某种方式更改枚举本身?例如使用反射。问题不是要更改枚举常量的状态。它即将更改枚举的常量集或删除任何常量。
关于以下枚举,是否可以添加颜色WHITE
或删除颜色RED
或更改其顺序?
public enum Color {
RED, GREEN, BLUE;
}
我为什么要问?
我知道这个问题有点恶意。但是,即使是约书亚·布洛赫(Joshua Bloch )在谈论实现单例(1)时也提到了“ 巧妙的攻击
”,并推荐了枚举单例模式。如果我们可以修改一个枚举,那么对这种模式的攻击是否可行?
我开始Color
使用分解枚举,开始进行分析javap -c
。以下是摘录:
static {};
Code:
0: new #1 // class playground/Color
3: dup
4: ldc #14 // String RED
6: iconst_0
7: invokespecial #15 // Method "<init>":(Ljava/lang/String;I)V
10: putstatic #19 // Field RED:Lplayground/Color;
13: new #1 // class playground/Color
16: dup
17: ldc #21 // String GREEN
19: iconst_1
20: invokespecial #15 // Method "<init>":(Ljava/lang/String;I)V
23: putstatic #22 // Field GREEN:Lplayground/Color;
26: new #1 // class playground/Color
29: dup
30: ldc #24 // String BLUE
32: iconst_2
33: invokespecial #15 // Method "<init>":(Ljava/lang/String;I)V
36: putstatic #25 // Field BLUE:Lplayground/Color;
39: iconst_3
40: anewarray #1 // class playground/Color
43: dup
44: iconst_0
45: getstatic #19 // Field RED:Lplayground/Color;
48: aastore
49: dup
50: iconst_1
51: getstatic #22 // Field GREEN:Lplayground/Color;
54: aastore
55: dup
56: iconst_2
57: getstatic #25 // Field BLUE:Lplayground/Color;
60: aastore
61: putstatic #27 // Field ENUM$VALUES:[Lplayground/Color;
64: return
在索引61处,我们看到将三个枚举常量分配给名称为的静态数组ENUM$VALUES
。
通过反射列出所有静态字段…
Field[] declaredFields = Color.class.getDeclaredFields();
for (Field field : declaredFields) {
if (Modifier.isStatic(field.getModifiers())) {
System.out.println(field.getName() + ": " + field.getType());
}
}
显示枚举常量并显示数组:
RED: class playground.ReflectEnum$Color
GREEN: class playground.ReflectEnum$Color
BLUE: class playground.ReflectEnum$Color
ENUM$VALUES: class [Lplayground.ReflectEnum$Color;
我定义了以下方法来获取枚举数组:
protected static <E extends Enum<E>> E[] getEnumsArray(Class<E> ec) throws Exception {
Field field = ec.getDeclaredField("ENUM$VALUES");
field.setAccessible(true);
return (E[]) field.get(ec);
}
使用它可以更改枚举常量的顺序:
Color[] colors = getEnumsArray(Color.class);
colors[0] = Color.GREEN;
colors[1] = Color.RED;
colors[2] = Color.BLUE;
列出枚举常量
for (Color color : Color.values()) {
System.out.println(action + ":" + color.ordinal());
}
显示:
GREEN:1
RED:0
BLUE:2
显然,顺序已更改。
由于可以为数组分配值,因此分配也是有效的null
。
Color[] colors = getEnumsArray(Color.class);
colors[0] = Color.GREEN;
colors[1] = Color.RED;
colors[2] = null;
列出枚举常量显示:
GREEN:1
RED:0
Exception in thread "main" java.lang.NullPointerException
at playground.ReflectEnum.main(ReflectEnum.java:57)
如果我们尝试按名称查询枚举常量
System.out.println(Color.valueOf("GREEN"));
System.out.println(Color.valueOf("RED"));
System.out.println(Color.valueOf("BLUE"));
我们看到最后一次更改甚至打破了:
Exception in thread "main" java.lang.NullPointerException
at java.lang.Class.enumConstantDirectory(Class.java:3236)
at java.lang.Enum.valueOf(Enum.java:232)
at playground.Color.valueOf(Color.java:1)
at playground.ReflectEnum.main(ReflectEnum.java:48)
该行ReflectEnum.java:48
包含的上述打印语句Color.valueOf("GREEN")
。此枚举常量未设置为null
。因此,它完全打破了valueOf
方法Color
。
但是Enum.valueOf(Color.class, "BLUE")
仍然解析枚举常量Color.BLUE
。
由于枚举数组被声明为static final
我遵循的,因此使用Java反射来更改private static
final字段
来创建和设置新的枚举数组。
protected static <E extends Enum<E>> void setEnumsArray(Class<E> ec, E... e) throws Exception {
Field field = ec.getDeclaredField("ENUM$VALUES");
Field modifiersField = Field.class.getDeclaredField("modifiers");
field.setAccessible(true);
modifiersField.setAccessible(true);
modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
field.set(ec, e);
}
但是执行setEnumsArray(Color.class, Color.BLUE, Color.GREEN, Color.RED, Color.BLUE)
失败,
Exception in thread "main" java.lang.IllegalAccessException: Can not set static final [Lplayground.Color; field playground.Color.ENUM$VALUES to [Lplayground.Color;
at sun.reflect.UnsafeFieldAccessorImpl.throwFinalFieldIllegalAccessException(UnsafeFieldAccessorImpl.java:76)
at sun.reflect.UnsafeFieldAccessorImpl.throwFinalFieldIllegalAccessException(UnsafeFieldAccessorImpl.java:80)
at sun.reflect.UnsafeQualifiedStaticObjectFieldAccessorImpl.set(UnsafeQualifiedStaticObjectFieldAccessorImpl.java:77)
at java.lang.reflect.Field.set(Field.java:758)
at playground.ReflectEnum.setEnumsArray(ReflectEnum.java:76)
at playground.ReflectEnum.main(ReflectEnum.java:37)
编辑1(在Radiodef的评论之后):
添加以下两种方法后…
protected static Field getEnumsArrayField(Class<?> ec) throws Exception {
Field field = ec.getDeclaredField("ENUM$VALUES");
field.setAccessible(true);
return field;
}
protected static void clearFieldAccessors(Field field) throws ReflectiveOperationException {
Field fa = Field.class.getDeclaredField("fieldAccessor");
fa.setAccessible(true);
fa.set(field, null);
Field ofa = Field.class.getDeclaredField("overrideFieldAccessor");
ofa.setAccessible(true);
ofa.set(field, null);
Field rf = Field.class.getDeclaredField("root");
rf.setAccessible(true);
Field root = (Field) rf.get(field);
if (root != null) {
clearFieldAccessors(root);
}
我尝试了这个:
System.out.println(Arrays.toString((Object[]) getEnumsArrayField(Color.class).get(null)));
clearFieldAccessors(getEnumsArrayField(Color.class));
setEnumsArray(Color.class, Color.BLUE, Color.GREEN, Color.RED, Color.BLUE);
System.out.println(Arrays.toString(Color.values()));
由此可见:
[RED, GREEN, BLUE]
[BLUE, GREEN, RED, BLUE]
枚举数组已被另一个替换。
编辑2(在GotoFinal的注释之根据GotoFinal的答案,关于如何在Java中使用反射创建枚举实例?有可能在运行时创建更多的枚举实例。然后应该可以用另一个实例代替一个枚举实例。我有以下枚举单例:
public enum Singleton {
INSTANCE("The one and only");
private String description;
private Singleton(String description) {
this.description = description;
}
@Override
public String toString() {
return description;
}
}
重用代码GotoFinal已显示我定义了以下方法:
protected static Singleton createEnumValue(String name, int ordinal, String description) throws Exception {
Class<Singleton> monsterClass = Singleton.class;
Constructor<?> constructor = monsterClass.getDeclaredConstructors()[0];
constructor.setAccessible(true);
Field constructorAccessorField = Constructor.class.getDeclaredField("constructorAccessor");
constructorAccessorField.setAccessible(true);
sun.reflect.ConstructorAccessor ca = (sun.reflect.ConstructorAccessor) constructorAccessorField.get(constructor);
if (ca == null) {
Method acquireConstructorAccessorMethod = Constructor.class.getDeclaredMethod("acquireConstructorAccessor");
acquireConstructorAccessorMethod.setAccessible(true);
ca = (sun.reflect.ConstructorAccessor) acquireConstructorAccessorMethod.invoke(constructor);
}
Singleton enumValue = (Singleton) ca.newInstance(new Object[] { name, ordinal, description });
return enumValue;
}
protected static <E extends Enum<E>> void setFinalField(Class<E> ec, Field field, E e) throws NoSuchFieldException, IllegalAccessException {
Field modifiersField = Field.class.getDeclaredField("modifiers");
field.setAccessible(true);
modifiersField.setAccessible(true);
modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
field.set(ec, e);
}
现在执行
System.out.println(Singleton.INSTANCE.toString());
// setting INSTANCE = theNewOne
Singleton theNewOne = createEnumValue(Singleton.INSTANCE.name(), Singleton.INSTANCE.ordinal(), "The new one!");
setFinalField(Singleton.class, Singleton.class.getDeclaredField(Singleton.INSTANCE.name()), theNewOne);
System.out.println(Singleton.INSTANCE.toString());
// setting enum array = [theNewOne]
clearFieldAccessors(getEnumsArrayField(Singleton.class));
setEnumsArray(Singleton.class, theNewOne);
System.out.println(Arrays.toString(Singleton.values()));
显示:
The one and only
The new one!
[The new one!]
总结:
可以在运行时修改一个枚举,并用另一个替换该枚举数组。但是至少设置一个枚举常量会null
破坏VM中定义的枚举的一致性。尽管可以根据GotoFinal的答案解决此问题。
使用实现单例时enum
,可以用另一个枚举实例替换唯一的实例-至少对于知道其实现的某些JDK / JRE而言。如果枚举构造函数具有参数,则新创建的枚举实例可以利用它们来植入恶意行为。
问题内容: 我正在阅读Swift浏览文档,并且遇到了问题。这是代码: 该功能将无法正常工作,我想知道是否有一种方法可以更改枚举的关联值,以及如何更改? 问题答案: 最直接的问题是,当您应该使用声明不可变变量(用声明)时,您尝试更改其值。但是,由于您的变量包含关联值的副本,因此,这不会解决此特定问题,但是通常这是您需要注意的。 如果要解决此问题,则需要将该函数声明为muting函数,并根据具体情况将
问题内容: 在Java中有可能发生这种情况吗?可以为Java中的枚举元素分配自定义数值吗? 问题答案:
问题内容: 是否可以在Java枚举中使用,还是需要使用?在我的测试中,它始终有效,但是我不确定是否可以保证。特别是,在枚举上没有方法,因此我不知道是否有可能获得一个枚举,该枚举将返回不同于的值。 例如,这样可以吗: 还是我需要这样写: 问题答案: 仅需2美分:这是Sun发布的Enum.java的代码,并且是JDK的一部分:
问题内容: 无论如何,通过将其与给定的字符串进行比较来检查是否存在枚举?我似乎找不到任何这样的功能。我可以尝试使用该方法并捕获异常,但是据我了解,捕获运行时异常不是一种好习惯。有人有什么想法吗? 问题答案: 我不认为没有捕获异常的内置方法。您可以改用如下方式: 编辑: 正如乔恩·斯凯特(Jon Skeet)所述,每次调用时,都会通过克隆私有支持数组来工作。如果性能至关重要,则可能只需要调用一次,缓
问题内容: 我有一个简单的收藏问题。我有一个对象。我想要一个的的中,且s 。我需要一个,因为我要覆盖专门返回的方法。最干净/最好的方法是什么? 问题答案: 编辑:无需编写自己的(尽管为了后代,我将在下面保留实现)-请参见Kevin Bourrillion在JDK中的答案。 如果您 确实 需要枚举,可以使用: 如果可能的话,最好使用… 更好的选择是在周围编写一个小型包装器类。这样一来,您不必为了发现
问题内容: Python 3.4引入了一个新模块,该模块向该语言添加了枚举类型。的文档提供了一个示例来演示如何扩展它: 此示例还演示了一个问题:在property方法中,定义了一个常量,该常量通常在类级别定义- 但是尝试在a级别内执行此操作只会将其添加为枚举的成员之一,因此,在方法内部定义。 如果该类想在其他方法中使用此常量,则也必须在其中定义它,这显然不是理想的选择。 有什么方法可以在中定义类常