Javaでは、それは、変更したり、列挙型自体を変更することができ、したがって、列挙型のシングルトン壊れにありますか?

ルシオ:

それは何とか実行時に列挙自体を変更することは可能ですか?リフレクションを使用した例。質問は、enum定数の状態を変更することに関するものではありません。これは、列挙型の定数のセットを変更するか、いずれかの定数を削除することです。

以下の列挙型については、カラーを追加することが可能であるWHITEか、削除、色をREDまたはその順序を変更するには?

public enum Color {

  RED, GREEN, BLUE;

}

なぜ私が尋ねますか?

私はこの質問には悪質なの一種である知っています。しかし、たとえジョシュア・ブロックは「言及巧みに細工攻撃をシングルトンの実装について(1)を話すとき」と列挙シングルトンパターンをお勧めします。私たちは、列挙型を変更することができるならば、実現可能なこのパターンの攻撃ですか?

私はそれを仕事にしようと、部分的にそれを管理していました。私は答えとして私の結果を掲載します-これ以下のアドバイス


(1)を参照してください。Javaでのシングルトンパターンを実装するための効率的な方法は何ですか?リンクがあり従うeffective_java_reloaded.pdf、31ページ。

ルシオ:

私は、列挙型を分解し、私の分析を開始した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で我々は、名前の静的な配列に割り当てられている3つのenum定数を参照してくださいENUM$VALUES

反射によってすべての静的フィールドをリスト...

Field[] declaredFields = Color.class.getDeclaredFields();
for (Field field : declaredFields) {
  if (Modifier.isStatic(field.getModifiers())) {
    System.out.println(field.getName() + ": " + field.getType());
  }
}

enum定数を示し、配列を明らかに:

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);
  }

それを使用するには、enum定数の順序を変更することも可能です。

Color[] colors = getEnumsArray(Color.class);
colors[0] = Color.GREEN;
colors[1] = Color.RED;
colors[2] = Color.BLUE;

enum定数をリスト

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;

enum定数番組をリスト:

GREEN:1
RED:0
Exception in thread "main" java.lang.NullPointerException
    at playground.ReflectEnum.main(ReflectEnum.java:57)

そして、私たちはその名前でenum定数ルックアップをしようとした場合

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上記のprint文が含まれていますColor.valueOf("GREEN")この列挙型の定数は次のように設定されていませんnullだから、破ったvalueOfの方法Colorを完全に。

しかしEnum.valueOf(Color.class, "BLUE")、まだ列挙型定数を解決しますColor.BLUE

列挙型配列が宣言されているのでstatic final、私は、その後のJavaリフレクションを使用して変更するプライベート静的最終フィールドの作成には、新たな列挙型配列を設定します。

  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)

EDIT 1(のコメントの後Radiodef):

私は、次の2つのメソッドを追加した後...

  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]

列挙型の配列は、別のものに置き換えられています。


EDIT 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!]

要約:

  • これは、実行時に列挙型を変更すると、別の1で列挙型の配列を交換することが可能です。しかし、少なくともに列挙型定数を設定するnull休憩VM内で定義された列挙型の一貫性を。このもののGotoFinalの答えに応じて固定することができます。

  • 使用してシングルトンを実装する場合enum、少なくともその実装を知って、いくつかのJDK / JREのために-それは別の列挙型のインスタンスでインスタンスを1つだけ交換することが可能です。列挙型のコンストラクタに引数がある場合、それらは、悪質な動作を植えるために、新しい作成した列挙型のインスタンスによって悪用される可能性があります。

おすすめ

転載: http://43.154.161.224:23101/article/api/json?id=183506&siteId=1