我试图private static final使用反射来更改字段的值(是的,我知道这可能是一个非常糟糕的主意)。\n而且,在大多数情况下,使用以下代码它可以正常工作:
import java.lang.reflect.Field;\nimport java.lang.reflect.Modifier;\n\npublic class A {\n\n public static void main(String[] args) throws ReflectiveOperationException {\n System.out.println("Before :: " + B.get());\n Field field = B.class.getDeclaredField("arr");\n field.setAccessible(true);\n // System.out.println("Peek :: " + ((String[]) field.get(null))[0]);\n Field modifiersField = Field.class.getDeclaredField("modifiers");\n modifiersField.setAccessible(true);\n modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);\n field.set(null, new String[] { "Good bye, World!" });\n System.out.println("After :: " + B.get());\n }\n}\n\nclass B {\n private static final String[] arr = new String[] { "Hello, World!" };\n\n public static String get() {\n return arr[0];\n }\n}\nRun Code Online (Sandbox Code Playgroud)\n\n按预期打印:
\n\nBefore :: Hello, World!\nAfter :: Good bye, World!\nRun Code Online (Sandbox Code Playgroud)\n\nget当我在设置字段值之前尝试通过反射set获取字段值时,就会出现问题。\n也就是说,如果我取消注释上面示例中的注释行,我会得到以下结果:
Before :: Hello, World!\nPeek :: Hello, World!\nException in thread "main" java.lang.IllegalAccessException: Can not set static final [Ljava.lang.String; field B.arr to [Ljava.lang.String;\n at sun.reflect.UnsafeFieldAccessorImpl.throwFinalFieldIllegalAccessException(UnsafeFieldAccessorImpl.java:76)\n at sun.reflect.UnsafeFieldAccessorImpl.throwFinalFieldIllegalAccessException(UnsafeFieldAccessorImpl.java:80)\n at sun.reflect.UnsafeQualifiedStaticObjectFieldAccessorImpl.set(UnsafeQualifiedStaticObjectFieldAccessorImpl.java:77)\n at java.lang.reflect.Field.set(Field.java:764)\n at A.main(A.java:14)\nRun Code Online (Sandbox Code Playgroud)\n\n为什么会发生这种情况?\n我尝试accessible在调用后再次设置标志get,但它没有帮助。\n我尝试了许多其他似乎也没有帮助的事情...
感谢您的帮助!
\n\n编辑:使用反射更改静态最终 File.separatorChar 进行单元测试中有一个答案元素?(请参阅@Rog\xc3\xa9rio 的“重要更新”)。
\n\n\n\n\n重要更新:上述解决方案并不适用于所有情况。如果该字段在重置之前可以通过反射进行访问和读取,则会
\n\nIllegalAccessException抛出异常。它失败是因为 Reflection API 创建了FieldAccessor缓存和重用的内部对象(请参阅 java.lang.reflect.Field#acquireFieldAccessor(boolean) 实现)。\n 失败的示例测试代码:Run Code Online (Sandbox Code Playgroud)\nField f = File.class.getField("separatorChar"); f.setAccessible(true); f.get(null);\n// call setFinalStatic as before: throws IllegalAccessException\n
遗憾的是,它没有说明如何解决这个问题......我如何“重置”该字段?
\n我不知道在回答这个问题时应该深入细节。但这里是所发生情况的简短摘要:
当您进行反射Field#get调用时,调用将在内部(经过几次安全检查后)委托给sun.reflect.FieldAccessor. 这是一个内部接口,顾名思义,提供对字段值的访问。内部使用的实例FieldAccessor是惰性创建的,“缓存”以供以后使用,甚至在多个Field实例之间共享。
该接口有许多不同的实现FieldAccessor。这些实现专门用于普通字段、静态字段或可通过调用访问的私有字段的各种情况setAccessible(true)。例如,在您的情况下,有一个UnsafeQualifiedStaticObjectFieldAccessorImpl涉及,并且名称已经表明这只是几十个专业之一。
其中许多FieldAccessor实现存储内部状态,它描述字段的一些属性。例如,该字段是“只读”还是final(!)。
要点是:FieldAccessor执行反射Field#get调用时创建的 与稍后将用于反射调用的相同Field#set。但当创建时FieldAccessor,该字段仍被视为final。仅在创建FieldAccessor.
因此,最简单的解决方案是确保在执行第一次反射调用之前final更改该字段的状态。这样,内部创建的 就是在“非最终”字段上运行的:FieldAccessor
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
public class A {
public static void main(String[] args) throws Exception {
System.out.println("Before :: " + B.get());
Field field = B.class.getDeclaredField("arr");
field.setAccessible(true);
Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
System.out.println("Peek :: " + ((String[]) field.get(null))[0]);
field.set(null, new String[] { "Good bye, World!" });
System.out.println("After :: " + B.get());
}
}
class B {
private static final String[] arr = new String[] { "Hello, World!" };
public static String get() {
return arr[0];
}
}
Run Code Online (Sandbox Code Playgroud)
我不应该提及这一点。人们会这样做。但无论如何:
从技术上讲,还可以brutallyHackYourWayThroughInternalClasses修改FieldAccessor内部创建的 ,以便随后允许修改该字段,即使它最初是为该final字段的版本创建的:
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
public class A {
public static void main(String[] args) throws Exception {
System.out.println("Before :: " + B.get());
Field field = B.class.getDeclaredField("arr");
field.setAccessible(true);
System.out.println("Peek :: " + ((String[]) field.get(null))[0]);
brutallyHackYourWayThroughInternalClasses(field);
Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
field.set(null, new String[] { "Good bye, World!" });
System.out.println("After :: " + B.get());
}
private static void brutallyHackYourWayThroughInternalClasses(Field field)
throws Exception
{
Field overrideFieldAccessorField =
Field.class.getDeclaredField("overrideFieldAccessor");
overrideFieldAccessorField.setAccessible(true);
Object overrideFieldAccessorValue =
overrideFieldAccessorField.get(field);
Class<?> unsafeFieldAccessorImplClass =
Class.forName("sun.reflect.UnsafeFieldAccessorImpl");
Field isFinalField =
unsafeFieldAccessorImplClass.getDeclaredField("isFinal");
isFinalField.setAccessible(true);
isFinalField.set(overrideFieldAccessorValue, false);
Class<?> unsafeQualifiedStaticFieldAccessorImplClass =
Class.forName("sun.reflect.UnsafeQualifiedStaticFieldAccessorImpl");
Field isReadOnlyField =
unsafeQualifiedStaticFieldAccessorImplClass.getDeclaredField(
"isReadOnly");
isReadOnlyField.setAccessible(true);
isReadOnlyField.set(overrideFieldAccessorValue, false);
}
}
class B {
private static final String[] arr = new String[] { "Hello, World!" };
public static String get() {
return arr[0];
}
}
Run Code Online (Sandbox Code Playgroud)