我有一个带有私有静态final字段的类,不幸的是,我需要在运行时更改它。
使用反射我得到这个错误:java.lang.IllegalAccessException:不能设置静态最终布尔字段
有什么方法可以改变这个值吗?
Field hack = WarpTransform2D.class.getDeclaredField("USE_HACK");
hack.setAccessible(true);
hack.set(null, true);
我有一个带有私有静态final字段的类,不幸的是,我需要在运行时更改它。
使用反射我得到这个错误:java.lang.IllegalAccessException:不能设置静态最终布尔字段
有什么方法可以改变这个值吗?
Field hack = WarpTransform2D.class.getDeclaredField("USE_HACK");
hack.setAccessible(true);
hack.set(null, true);
当前回答
从Java 12开始,给出的答案将不能工作。
下面是一个关于如何修改Java 12以来的私有静态final字段的示例(基于这个答案)。
private Object modifyField(Object newFieldValue, String fieldName, Object classInstance) throws NoSuchFieldException, IllegalAccessException {
Field field = classInstance.getClass().getDeclaredField(fieldName);
VarHandle MODIFIERS;
field.setAccessible(true);
var lookup = MethodHandles.privateLookupIn(Field.class, MethodHandles.lookup());
MODIFIERS = lookup.findVarHandle(Field.class, "modifiers", int.class);
int mods = field.getModifiers();
if (Modifier.isFinal(mods)) {
MODIFIERS.set(field, mods & ~Modifier.FINAL);
}
Object previousValue = field.get(classInstance);
field.set(null, newFieldValue);
return previousValue;
}
更多细节请参见这篇文章。
其他回答
我还集成了joor库
只使用
Reflect.on(yourObject).set("finalFieldName", finalFieldValue);
我还修复了一个覆盖的问题,以前的解决方案似乎错过了。 但是,只有在没有其他好的解决方案时,才要小心使用这种方法。
这里的许多答案都很有用,但我发现没有一个在Android上特别适用。我甚至是joor的Reflect的忠实用户,无论是它还是apache的FieldUtils——在这里的一些答案中都提到了这一点。
Android的问题
这样做的根本原因是,在Android上,field类中没有修饰符字段,这使得任何涉及这段代码的建议(如在标记的答案中)都是无用的:
Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
事实上,引用FieldUtils.removeFinalModifier():
// Do all JREs implement Field with a private ivar called "modifiers"?
final Field modifiersField = Field.class.getDeclaredField("modifiers");
所以,答案是否定的……
解决方案
非常简单——字段名是accessFlags,而不是修饰符。这招很管用:
Field accessFlagsField = Field.class.getDeclaredField("accessFlags");
accessFlagsField.setAccessible(true);
accessFlagsField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
旁注#1:无论字段在类中是否是静态的,这都可以工作。
旁注#2:鉴于字段本身可能是私有的,建议也启用对字段本身的访问,使用field. setaccessible (true)(除了accessFlagsField.setAccessible(true))。
在存在安全管理器的情况下,可以使用AccessController.doPrivileged
从上面接受的答案中取同样的例子:
import java.lang.reflect.*;
public class EverythingIsTrue {
static void setFinalStatic(Field field, Object newValue) throws Exception {
field.setAccessible(true);
Field modifiersField = Field.class.getDeclaredField("modifiers");
// wrapping setAccessible
AccessController.doPrivileged(new PrivilegedAction() {
@Override
public Object run() {
modifiersField.setAccessible(true);
return null;
}
});
modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
field.set(null, newValue);
}
public static void main(String args[]) throws Exception {
setFinalStatic(Boolean.class.getField("FALSE"), true);
System.out.format("Everything is %s", false); // "Everything is true"
}
}
在lambda表达式中,AccessController。doPrivileged,可以简化为:
AccessController.doPrivileged((PrivilegedAction) () -> {
modifiersField.setAccessible(true);
return null;
});
在部署到JDK 1.8u91之前,接受的答案对我来说是有效的。 然后我意识到它在野外失败了。集(null, newValue);行,当我在调用setFinalStatic方法之前通过反射读取值。
可能读取导致了Java反射内部的某种不同设置(即失败情况下的sun.reflect.UnsafeQualifiedStaticObjectFieldAccessorImpl,而不是成功情况下的sun.reflect.UnsafeStaticObjectFieldAccessorImpl),但我没有进一步详细说明。
因为我需要在旧值的基础上临时设置新值,然后再将旧值设置回来,所以我对signature做了一点改变,在外部提供计算功能的同时也返回旧值:
public static <T> T assignFinalField(Object object, Class<?> clazz, String fieldName, UnaryOperator<T> newValueFunction) {
Field f = null, ff = null;
try {
f = clazz.getDeclaredField(fieldName);
final int oldM = f.getModifiers();
final int newM = oldM & ~Modifier.FINAL;
ff = Field.class.getDeclaredField("modifiers");
ff.setAccessible(true);
ff.setInt(f,newM);
f.setAccessible(true);
T result = (T)f.get(object);
T newValue = newValueFunction.apply(result);
f.set(object,newValue);
ff.setInt(f,oldM);
return result;
} ...
然而,对于一般情况,这是不够的。
final字段的全部意义在于一旦设置它就不能重新分配。JVM使用这个保证来维护各个地方的一致性(例如内部类引用外部变量)。所以没有。如果能够这样做,就会破坏JVM!
解决办法不是一开始就宣布它是最终的。