当前位置: 首页 > 面试题库 >

在Java中,是否可以更改或修改枚举本身,从而破坏枚举单例?

高恺
2023-03-14
问题内容

是否可以在运行时以某种方式更改枚举本身?例如使用反射。问题不是要更改枚举常量的状态。它即将更改枚举的常量集或删除任何常量。

关于以下枚举,是否可以添加颜色WHITE或删除颜色RED或更改其顺序?

public enum Color {

  RED, GREEN, BLUE;

}

我为什么要问?

  • 首先,我想知道这是否可行。
  • 但是如果是这样,是否会对使用Enum实现单例的通用方式产生影响?

我知道这个问题有点恶意。但是,即使是约书亚·布洛赫(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级别内执行此操作只会将其添加为枚举的成员之一,因此,在方法内部定义。 如果该类想在其他方法中使用此常量,则也必须在其中定义它,这显然不是理想的选择。 有什么方法可以在中定义类常