2017-03-27 79 views
-1

我在参照以下文章了解反射枚举如何使用反射来返回方法中的“添加”枚举值?

https://www.niceideas.ch/roller2/badtrash/entry/java_create_enum_instances_dynamically

而且相应的源代码:

import java.lang.reflect.AccessibleObject; 
import java.lang.reflect.Array; 
import java.lang.reflect.Field; 
import java.lang.reflect.Modifier; 
import java.util.ArrayList; 
import java.util.Arrays; 
import java.util.List; 

import sun.reflect.ConstructorAccessor; 
import sun.reflect.FieldAccessor; 
import sun.reflect.ReflectionFactory; 

public class ReflectionUtils { 

    private static ReflectionFactory reflectionFactory = ReflectionFactory.getReflectionFactory(); 

    private static void setFailsafeFieldValue(Field field, Object target, Object value) throws NoSuchFieldException, 
      IllegalAccessException { 

     // let's make the field accessible 
     field.setAccessible(true); 

     // next we change the modifier in the Field instance to 
     // not be final anymore, thus tricking reflection into 
     // letting us modify the static final field 
     Field modifiersField = Field.class.getDeclaredField("modifiers"); 
     modifiersField.setAccessible(true); 
     int modifiers = modifiersField.getInt(field); 

     // blank out the final bit in the modifiers int 
     modifiers &= ~Modifier.FINAL; 
     modifiersField.setInt(field, modifiers); 

     FieldAccessor fa = reflectionFactory.newFieldAccessor(field, false); 
     fa.set(target, value); 
    } 

    private static void blankField(Class<?> enumClass, String fieldName) throws NoSuchFieldException, 
      IllegalAccessException { 
     for (Field field : Class.class.getDeclaredFields()) { 
      if (field.getName().contains(fieldName)) { 
       AccessibleObject.setAccessible(new Field[]{field}, true); 
       setFailsafeFieldValue(field, enumClass, null); 
       break; 
      } 
     } 
    } 

    private static void cleanEnumCache(Class<?> enumClass) throws NoSuchFieldException, IllegalAccessException { 
     blankField(enumClass, "enumConstantDirectory"); // Sun (Oracle?!?) JDK 1.5/6 
     blankField(enumClass, "enumConstants"); // IBM JDK 
    } 

    private static ConstructorAccessor getConstructorAccessor(Class<?> enumClass, Class<?>[] additionalParameterTypes) 
      throws NoSuchMethodException { 
     Class<?>[] parameterTypes = new Class[additionalParameterTypes.length + 2]; 
     parameterTypes[0] = String.class; 
     parameterTypes[1] = int.class; 
     System.arraycopy(additionalParameterTypes, 0, parameterTypes, 2, additionalParameterTypes.length); 
     return reflectionFactory.newConstructorAccessor(enumClass.getDeclaredConstructor(parameterTypes)); 
    } 

    private static Object makeEnum(Class<?> enumClass, String value, int ordinal, Class<?>[] additionalTypes, 
            Object[] additionalValues) throws Exception { 
     Object[] parms = new Object[additionalValues.length + 2]; 
     parms[0] = value; 
     parms[1] = Integer.valueOf(ordinal); 
     System.arraycopy(additionalValues, 0, parms, 2, additionalValues.length); 
     return enumClass.cast(getConstructorAccessor(enumClass, additionalTypes).newInstance(parms)); 
    } 

    /** 
    * Add an enum instance to the enum class given as argument 
    * 
    * @param <T>  the type of the enum (implicit) 
    * @param enumType the class of the enum to be modified 
    * @param enumName the name of the new enum instance to be added to the class. 
    */ 
    @SuppressWarnings("unchecked") 
    public static <T extends Enum<?>> void addEnum(Class<T> enumType, String enumName) { 

     // 0. Sanity checks 
     if (!Enum.class.isAssignableFrom(enumType)) { 
      throw new RuntimeException("class " + enumType + " is not an instance of Enum"); 
     } 

     // 1. Lookup "$VALUES" holder in enum class and get previous enum instances 
     Field valuesField = null; 
     Field[] fields = TestEnum.class.getDeclaredFields(); 
     for (Field field : fields) { 
      if (field.getName().contains("$VALUES")) { 
       valuesField = field; 
       break; 
      } 
     } 
     AccessibleObject.setAccessible(new Field[]{valuesField}, true); 

     try { 

      // 2. Copy it 
      T[] previousValues = (T[]) valuesField.get(enumType); 
      List<T> values = new ArrayList<T>(Arrays.asList(previousValues)); 

      // 3. build new enum 
      T newValue = (T) makeEnum(enumType, // The target enum class 
        enumName, // THE NEW ENUM INSTANCE TO BE DYNAMICALLY ADDED 
        values.size(), 
        new Class<?>[]{}, // could be used to pass values to the enum constuctor if needed 
        new Object[]{}); // could be used to pass values to the enum constuctor if needed 

      // 4. add new value 
      values.add(newValue); 

      // 5. Set new values field 
      setFailsafeFieldValue(valuesField, null, values.toArray((T[]) Array.newInstance(enumType, 0))); 

      // 6. Clean enum cache 
      cleanEnumCache(enumType); 

     } catch (Exception e) { 
      e.printStackTrace(); 
      throw new RuntimeException(e.getMessage(), e); 
     } 
    } 

    private static enum TestEnum { 
     a, 
     b, 
     c; 
    } 

    public static void main(String[] args) { 

     // Dynamically add 3 new enum instances d, e, f to TestEnum 
     addEnum(TestEnum.class, "d"); 
     addEnum(TestEnum.class, "e"); 
     addEnum(TestEnum.class, "f"); 

     // Run a few tests just to show it works OK. 
     System.out.println(Arrays.deepToString(TestEnum.values())); 
     // Shows : [a, b, c, d, e, f] 
    } 
} 

不知何故,我需要返回新的一个来自方法的枚举值:

public TestEnum theValue() { 
    return TestEnum.f; 
} 

当然,这不会编译。如何从上述方法返回f(这是添加的枚举值之一)?

编辑

我的线沿线的思考的东西:

private TestEnum testEnum; 

@Override 
public TestEnum theValue() { 
    ReflectionUtils.addEnum(TestEnum.class, "f"); 
    //How can I set the testEnum field to have 'f' as a value? 
    return this.testEnum; 
} 
+1

这段代码真的很糟糕。它依赖于Java的特定实现的知识,如果在不同的实现上尝试它(例如,Android,既不是Oracle也不是IBM),它可能会中断。无论如何,无论你使用反射创建,你必须使用反射。这强烈建议你应该考虑*不*使用枚举。 – RealSkeptic

+0

感谢RealSkeptic。我很欣赏使用** sun **软件包的风险......我正在考虑将此仅引入到测试软件包中。 – balteo

+0

我已编辑我的帖子。有人可以帮忙吗? – balteo

回答

1

public static <T extends Enum<?>> void addEnum(Class<T> enumType, String enumName) 

修改你的方法

public static <T extends Enum<?>> T addEnum(Class<T> enumType, String enumName) 

并从此方法返回值。

// 6. Clean enum cache 
cleanEnumCache(enumType); 

// 7. Clean enum cache 
return newvalue; 

并从异常块返回newvalue

然而,像大多数其他评论者指出的那样,我做这样的反思似乎不是一个好主意。如果这不是因为不可修改的第三方来源,那么您应该重新设计您的问题,以便在没有这种枚举的情况下工作。

+0

基本上我的生产代码(src/main/java)依赖于枚举的返回值,我们希望在测试中引入一个新的枚举值(src/test/java)用于测试目的。上面的反射应用程序永远不会生成代码。 – balteo

+0

使用反射枚举进行测试 - 对我来说似乎很合理。 – CoronA