有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

java为枚举变量设置最后一个字段

我想更改在我的枚举类中使用反射声明的属性“name”的最终值,将其与枚举本身的名称一起使用,以便在自定义注释中使用它。但我面临着一种我无法调试的奇怪行为

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.Map;
import java.util.Set;


@Service
@Slf4j
public class EnumNameEditor {

    public static void throwErrorForNotDefinedErrorCodes() throws Exception {
        Reflections reflections = new Reflections("com.");
        Set<Class<? extends CustomClass>> classes = reflections.getSubTypesOf(CustomClass.class);
        log.info("classes: {}",classes);
        for (Class c : classes) {
            if(c.isEnum()) {
                changeNameInEnum(c);
            }
        }
    }

    private static<C extends Enum<C>> void changeNameInEnum(Class<C> c) throws Exception {
        C[] codes = c.getEnumConstants();
        String uniqueIdentifier = "uniqueIdentifier";
        String name = "name";
        log.info("c: {}",Arrays.asList(codes));
        for (C e : codes) {
            System.out.println("\n\n=============== changing: " + e.ordinal());
            getValue(c,name,e);
            setValue(c,e);
            System.out.println("Ee : " + e);
            getValue(c,name,e);
            setFieldValue(c,e);
        }

        codes = c.getEnumConstants();
        log.info("after c: {}",Arrays.asList(codes));
    }

    private static <C extends Enum<C>> void setFieldValue(Class<C> c, C e) throws Exception {
        System.out.println("e: "+ e);
        Field $VALUESField = c.getDeclaredField("$VALUES");
        makeAccessible($VALUESField);
        C[] oldValues = (C[]) $VALUESField.get(null);
        oldValues[e.ordinal()] = e;
        $VALUESField.set(null, oldValues);

        $VALUESField = Class.class.getDeclaredField("enumConstants");
        makeAccessible($VALUESField);
        $VALUESField.set(c, oldValues);

        try {
            $VALUESField = Class.class.getDeclaredField("enumConstantDirectory");
            makeAccessible($VALUESField);
            Map<String,C> map = (Map<String, C>) $VALUESField.get(c);
            System.out.println("map: " + map);
            if(map != null) {
                map.put(e.name(),e);
                $VALUESField.set(c, map);
            }

        } catch (Exception exc) {
            exc.printStackTrace();
            log.debug("exception while setting new enum values in enumConstantDirectory for class: {}",c);
        }


    }

    static<C extends Enum<C>> Object getValue(Class<C> c, String fname, C e) throws Exception {
        Field field = c.getDeclaredField(fname);
        makeAccessible(field);
        Object value = field.get(e);
        System.out.println("value defined: " + value + " for: " + fname);
        return value;
    }

    static<C extends Enum<C>> C setValue(Class<C> c, C e) throws Exception {
        Field field = c.getDeclaredField("name");
        makeAccessible(field);
        field.set(e,e.name());
        return e;
    }

    static void makeAccessible(Field field) throws Exception {
        field.setAccessible(true);
        Field modifiersField = Field.class.getDeclaredField("modifiers");
        modifiersField.setAccessible(true);
        modifiersField.setInt(field, field.getModifiers() & ~ Modifier.FINAL);
    }

    public static void main(String[] args) {
        try {
            System.out.println("name: " + GenericResponseErrorCodes.UNEXPECTED_ERROR.name);
            throwErrorForNotDefinedErrorCodes();
            System.out.println("name: " + GenericResponseErrorCodes.UNEXPECTED_ERROR.name);
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

}

我的枚举类如下

public enum  GenericResponseErrorCodes implements CustomClass {

    UNEXPECTED_ERROR(1,"Unexpected error has occurred. Please try again later."),
    UNEXPECTED_ERROR2(2,"Unexpected error2 has occurred. Please try again later."),
    UNEXPECTED_ERROR3(3,"Unexpected error3 has occurred. Please try again later.","def.prop"),
    UNEXPECTED_ERROR4(4,"Unexpected error4 has occurred. Please try again later."),
    UNEXPECTED_ERROR5(5,"Unexpected error5 has occurred. Please try again later.");



    final String uniqueIdentifier = "G";
    String key;
    String message;
    Integer code;
    public final String name = "test_name";

    GenericResponseErrorCodes( Integer code, String message) {
        this.code = code;
        this.message = message;
    }

    GenericResponseErrorCodes(Integer code, String message, String key) {
        this.code = code;
        this.key = key;
        this.message = message;
    }

}

当我执行主功能时,我会得到以下日志

name: test_name

c: [GenericResponseErrorCodes(uniqueIdentifier=G, key=null, message=Unexpected error has occurred. Please try again later., code=1, name=test_name), GenericResponseErrorCodes(uniqueIdentifier=G, key=null, message=Unexpected error2 has occurred. Please try again later., code=2, name=test_name), GenericResponseErrorCodes(uniqueIdentifier=G, key=def.prop, message=Unexpected error3 has occurred. Please try again later., code=3, name=test_name), GenericResponseErrorCodes(uniqueIdentifier=G, key=null, message=Unexpected error4 has occurred. Please try again later., code=4, name=test_name), GenericResponseErrorCodes(uniqueIdentifier=G, key=null, message=Unexpected error5 has occurred. Please try again later., code=5, name=test_name)]


=============== changing: 0
value defined: test_name for: name
Ee : GenericResponseErrorCodes(uniqueIdentifier=G, key=null, message=Unexpected error has occurred. Please try again later., code=1, name=test_name)
value defined: UNEXPECTED_ERROR for: name


=============== changing: 1
value defined: test_name for: name
Ee : GenericResponseErrorCodes(uniqueIdentifier=G, key=null, message=Unexpected error2 has occurred. Please try again later., code=2, name=test_name)
value defined: UNEXPECTED_ERROR2 for: name


=============== changing: 2
value defined: test_name for: name
Ee : GenericResponseErrorCodes(uniqueIdentifier=G, key=def.prop, message=Unexpected error3 has occurred. Please try again later., code=3, name=test_name)
value defined: UNEXPECTED_ERROR3 for: name


=============== changing: 3
value defined: test_name for: name
Ee : GenericResponseErrorCodes(uniqueIdentifier=G, key=null, message=Unexpected error4 has occurred. Please try again later., code=4, name=test_name)
value defined: UNEXPECTED_ERROR4 for: name


=============== changing: 4
value defined: test_name for: name
Ee : GenericResponseErrorCodes(uniqueIdentifier=G, key=null, message=Unexpected error5 has occurred. Please try again later., code=5, name=test_name)
value defined: UNEXPECTED_ERROR5 for: name

after c: [GenericResponseErrorCodes(uniqueIdentifier=G, key=null, message=Unexpected error has occurred. Please try again later., code=1, name=test_name), GenericResponseErrorCodes(uniqueIdentifier=G, key=null, message=Unexpected error2 has occurred. Please try again later., code=2, name=test_name), GenericResponseErrorCodes(uniqueIdentifier=G, key=def.prop, message=Unexpected error3 has occurred. Please try again later., code=3, name=test_name), GenericResponseErrorCodes(uniqueIdentifier=G, key=null, message=Unexpected error4 has occurred. Please try again later., code=4, name=test_name), GenericResponseErrorCodes(uniqueIdentifier=G, key=null, message=Unexpected error5 has occurred. Please try again later., code=5, name=test_name)]
name: test_name

Process finished with exit code 0

我无法确定为什么使用反射访问的名称与枚举对象中的名称不同?还有,我应该如何永久更改名称字段? 提前谢谢

PS:我从this博客上获取了参考资料


共 (1) 个答案

  1. # 1 楼答案

    final String name = "test_name";声明一个编译时常量

    在编译时,对该变量的所有(非反射)引用都将被"test_name"替换。因此,更改字段对这些访问没有影响,尤其是

    System.out.println("name: " + GenericResponseErrorCodes.UNEXPECTED_ERROR.name);
    

    已在编译时确定

    除此之外,90%的代码已经过时。您似乎不熟悉引用类型的概念。修改对象时,其标识不会更改,因此无需将引用写入已包含对其引用的变量

    存储在$VALUES中的数组仍然保存着对e.ordinal()索引中对象的引用,因此oldValues[e.ordinal()] = e;是过时的,但同样地,即使设置此数组元素有效果,$VALUES字段仍然引用了相同的数组,因此将此字段设置为它已经包含的相同数组引用,在任何情况下都是过时的

    这同样适用于存储在enumConstants(初始化时)中的映射。它仍然包含对同一对象的引用,无论是否已修改。读取映射以放置它已经包含的引用,同样地,将enumConstants设置为它已经引用的映射是过时的

    值得注意的是,这两个字段并不是JRE中保存有关枚举类型的缓存信息的唯一位置,但谢天谢地,如上所述,当您更改被引用对象的字段时,您不需要更新这些引用

    您所需要做的就是更改要修改的字段的声明,使其不会形成编译时常量,例如

    enum GenericResponseErrorCodes implements CustomClass {
        UNEXPECTED_ERROR(1,"Unexpected error has occurred. Please try again later."),
        UNEXPECTED_ERROR2(2,"Unexpected error2 has occurred. Please try again later."),
        UNEXPECTED_ERROR3(3,"Unexpected error3 has occurred. Please try again later.","def.prop"),
        UNEXPECTED_ERROR4(4,"Unexpected error4 has occurred. Please try again later."),
        UNEXPECTED_ERROR5(5,"Unexpected error5 has occurred. Please try again later.");
    
        final String uniqueIdentifier = "G";
        String key;
        String message;
        Integer code;
        public final String name;
    
        GenericResponseErrorCodes( Integer code, String message) {
            this.code = code;
            this.message = message;
            name = "test_name";
        }
    
        GenericResponseErrorCodes(Integer code, String message, String key) {
            this.code = code;
            this.key = key;
            this.message = message;
            name = "test_name";
        }
    }
    

    将赋值移动到构造函数的替代方法有

    public final String name = "test_name".toString();
    

    因此,它不是编译时常量,尽管我们知道toString()将简单地返回对字符串本身的引用

    或者

    public final String name; { name = "test_name"; }
    

    在这里,大括号定义了一个初始值设定项,它会自动复制到每个构造函数中

    然后,从你的反射操作中移除任何过时的东西,它变成:

    public class EnumNameEditor {
        public static void throwErrorForNotDefinedErrorCodes() throws Exception {
            Reflections reflections = new Reflections("com.");
            Set<Class<? extends CustomClass>> classes = reflections.getSubTypesOf(CustomClass.class);
            log.info("classes: {}",classes);
            for (Class c : classes) {
                if(c.isEnum()) {
                    changeNameInEnum(c);
                }
            }
        }
        private static<C extends Enum<C>> void changeNameInEnum(Class<C> c) throws Exception {
            C[] codes = c.getEnumConstants();
            log.info("c: {}", Arrays.asList(codes));
            Field name = c.getDeclaredField("name");
            name.setAccessible(true);
            for (C e : codes) {
                System.out.println("\n\n=============== changing: " + e.ordinal());
                System.out.println("value defined: " + name.get(e) + " for: " + name.getName());
                name.set(e, e.name());
                System.out.println("Ee : " + e);
                System.out.println("value defined: " + name.get(e) + " for: " + name.getName());
            }
            log.info("after c: {}", Arrays.asList(codes));
        }
        public static void main(String[] args) {
            try {
                System.out.println("name: " + GenericResponseErrorCodes.UNEXPECTED_ERROR.name);
                throwErrorForNotDefinedErrorCodes();            
                System.out.println("name: " + GenericResponseErrorCodes.UNEXPECTED_ERROR.name);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    

    它将产生与代码相同的输出,但最后一个print语句除外,它现在产生

    name: UNEXPECTED_ERROR
    

    如你所愿

    当然,你可以简单地使用

    enum  GenericResponseErrorCodes implements CustomClass {
        UNEXPECTED_ERROR(1,"Unexpected error has occurred. Please try again later."),
        UNEXPECTED_ERROR2(2,"Unexpected error2 has occurred. Please try again later."),
        UNEXPECTED_ERROR3(3,"Unexpected error3 has occurred. Please try again later.","def.prop"),
        UNEXPECTED_ERROR4(4,"Unexpected error4 has occurred. Please try again later."),
        UNEXPECTED_ERROR5(5,"Unexpected error5 has occurred. Please try again later.");
    
        final String uniqueIdentifier = "G";
        String key;
        String message;
        Integer code;
        public final String name = super.name();
    
        GenericResponseErrorCodes( Integer code, String message) {
            this.code = code;
            this.message = message;
        }
    
        GenericResponseErrorCodes(Integer code, String message, String key) {
            this.code = code;
            this.key = key;
            this.message = message;
        }
    }
    

    使name字段冗余地包含枚举常量的名称,而不进行任何反射操作