それは何とか実行時に列挙自体を変更することは可能ですか?リフレクションを使用した例。質問は、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つだけ交換することが可能です。列挙型のコンストラクタに引数がある場合、それらは、悪質な動作を植えるために、新しい作成した列挙型のインスタンスによって悪用される可能性があります。