我试图使用反射来改变private static final
字段的值(是的,这可能是一个非常糟糕的主意,我知道)。
而且,在大多数情况下,使用以下代码可以正常工作:
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
public class A {
public static void main(String[] args) throws ReflectiveOperationException {
System.out.println("Before :: " + B.get());
Field field = B.class.getDeclaredField("arr");
field.setAccessible(true);
// System.out.println("Peek :: " + ((String[]) field.get(null))[0]);
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());
}
}
class B {
private static final String[] arr = new String[] { "Hello, World!" };
public static String get() {
return arr[0];
}
}
按预期打印:
Before :: Hello, World!
After :: Good bye, World!
当我在get
之前通过反射尝试set
字段值时出现问题。
也就是说,如果我取消注释上面示例中的注释行,我会得到以下结果:
Before :: Hello, World!
Peek :: Hello, World!
Exception in thread "main" java.lang.IllegalAccessException: Can not set static final [Ljava.lang.String; field B.arr to [Ljava.lang.String;
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:764)
at A.main(A.java:14)
为什么会这样?
我在调用accessible
之后尝试再次设置get
标志,但它没有帮助。
我尝试了许多其他似乎没有帮助的事情......
感谢您的帮助!
修改:Using reflection to change static final File.separatorChar for unit testing?中有一个答案元素(请参阅@Rogério的“重要更新”)。
重要更新:上述解决方案在所有情况下均无效。如果在重置之前可以访问该字段并通过Reflection读取,则会引发
IllegalAccessException
。它失败是因为Reflection API创建了内部FieldAccessor
对象,这些对象被缓存并重用(请参阅java.lang.reflect.Field#acquireFieldAccessor(boolean)实现)。 示例测试代码失败:Field f = File.class.getField("separatorChar"); f.setAccessible(true); f.get(null); // call setFinalStatic as before: throws IllegalAccessException
可悲的是,它没有说明如何解决这个问题......如何“重置”这个领域?
答案 0 :(得分:2)
在回答这个问题时,我不确定应该对细节有多深入。但这里是对发生的事情的简短总结:
当您进行反思Field#get
调用时,调用将在内部(在几次安全检查之后)委派给sun.reflect.FieldAccessor
。这是一个内部接口,顾名思义,它提供对字段值的访问。内部使用的FieldAccessor
实例是懒惰创建的,"缓存"供以后使用,甚至在多个Field
实例之间共享。
FieldAccessor
接口有许多不同的实现。这些实现专门用于通过调用setAccessible(true)
可访问的普通字段,静态字段或私有字段的各种情况。例如,在您的情况下,涉及UnsafeQualifiedStaticObjectFieldAccessorImpl
,名称已经表明这只是几十个专业化中的一个。
这些FieldAccessor
实现中的许多都存储了一个内部状态,它描述了该字段的一些属性。例如,该字段是否为#read;"或 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];
}
}
。字段:
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];
}
}
创建的该字段的版本:
auth!=null