英文:
Setting a final field for an ENUM variable
问题
I understand that you want a translation of the provided code and logs. Here's the translated content:
我想使用反射更改我的枚举类中声明的属性 "name" 的最终值,将其更改为枚举本身的名称,以便在自定义注释中使用它。但我遇到了一个我无法调试的奇怪行为。
我的枚举类如下所示。
我在执行主函数时收到以下日志记录。
我无法确定为什么使用反射访问的名称与枚举对象中的名称不同?还有,我应该如何永久更改名称字段?
Please note that the code and logs you provided are quite extensive, and it might be more helpful to break down your questions or issues into smaller parts if you need further assistance with specific aspects of your code.
英文:
I want to change the final value of an attribute "name" declared in my enum class using reflection with the name of the enum itself to use it in custom annotations. But I am facing a strange behaviour which I am not able to debug.
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();
}
}
}
My Enum class is as below.
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;
}
}
I am getting the below logs when I execute the main function.
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
I am not able to identify why the name accessed using reflection is different from the one present in the enum object? Also how should I change the name field permanently?
Thanks in advance.
PS: I have taken reference from this blog.
答案1
得分: 1
final String name = "test_name"; declares a *compile-time constant*.
All (non-reflective) references to this variable will get replaced by "test_name" at compile-time already. So changing the field has no effect to these accesses, most notably, the result of
System.out.println("name: " + GenericResponseErrorCodes.UNEXPECTED_ERROR.name);
has been determined at compile-time already.
Besides that, 90% of your code is obsolete. It seems, you are not familiar with the concept of *reference types*. When you modify an object, its identity doesn’t change, so there is no need to write the reference to the variables already holding a reference to it.
The array stored in `$VALUES` still holds a reference to the object at the `e.ordinal()` index, so `oldValues[e.ordinal()] = e;` is obsolete, but likewise, even if setting this array element had an effect, the `$VALUES` field still referenced the same array, so setting this field to the same array reference it already contains, is obsolete in either case.
The same applies to the map stored in `enumConstants` (when initialized). It still contains a reference to the same object, whether modified or not. Reading the map to put the reference it already contains and likewise, setting the `enumConstants` to the map it does already reference, is obsolete.
It’s worth noting that these two fields are not the only places holding cached information about enumeration types in the JRE, but thankfully, as said, you don’t need to update these references when you change a field of the referenced object.
All you need to do, is to change the declaration of the field you want to modify in a way that it doesn’t form a compile-time constant, e.g.
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";
}
}
Alternatives to moving the assignment to the constructor are
public final String name = "test_name".toString();
which bears a runtime operation, hence, is not a compile-time constant, despite we know that `toString()` will simply return the reference to the string itself.
Or
public final String name; { name = "test_name"; }
Here, the curly braces define an initializer, which will get copied to every constructor automatically.
Then, removing anything obsolete from your reflective operation, it becomes:
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[] 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();
}
}
}
It will produce the same output as your code, except for the last print statement, which now produces
name: UNEXPECTED_ERROR
as intended.
Of course, you could simply use
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;
}
}
to have a `name` field redundantly containing the name of the enum constant, without any reflective operation.
英文:
final String name = "test_name";
declares a compile-time constant.
All (non-reflective) references to this variable will get replaced by "test_name"
at compile-time already. So changing the field has no effect to these accesses, most notably, the result of
System.out.println("name: " + GenericResponseErrorCodes.UNEXPECTED_ERROR.name);
has been determined at compile-time already.
Besides that, 90% of your code is obsolete. It seems, you are not familiar with the concept of reference types. When you modify an object, its identity doesn’t change, so there is no need to write the reference to the variables already holding a reference to it.
The array stored in $VALUES
still holds a reference to the object at the e.ordinal()
index, so oldValues[e.ordinal()] = e;
is obsolete, but likewise, even if setting this array element had an effect, the $VALUES
field still referenced the same array, so setting this field to the same array reference it already contains, is obsolete in either case.
The same applies to the map stored in enumConstants
(when initialized). It still contains a reference to the same object, whether modified or not. Reading the map to put the reference it already contains and likewise, setting the enumConstants
to the map it does already reference, is obsolete.
It’s worth noting that these two fields are not the only places holding cached information about enumeration types in the JRE, but thankfully, as said, you don’t need to update these references when you change a field of the referenced object.
All you need to do, is to change the declaration of the field you want to modify in a way that it doesn’t form a compile-time constant, e.g.
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";
}
}
Alternatives to moving the assignment to the constructor are
public final String name = "test_name".toString();
which bears a runtime operation, hence, is not a compile-time constant, despite we know that toString()
will simply return the reference to the string itself.
Or
public final String name; { name = "test_name"; }
Here, the curly braces define an initializer, which will get copied to every constructor automatically.
Then, removing anything obsolete from your reflective operation, it becomes:
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();
}
}
}
It will produce the same output as your code, except for the last print statement, which now produces
name: UNEXPECTED_ERROR
as intended.
Of course, you could simply use
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;
}
}
to have a name
field redundantly containing the name of the enum constant, without any reflective operation.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论