我有一个带有私有静态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);
当前回答
这里的许多答案都很有用,但我发现没有一个在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))。
其他回答
除了排名靠前的答案,你还可以使用最简单的方法。Apache公共的FieldUtils类已经有特定的方法可以做这些事情。请看看FieldUtils。removeFinalModifier方法。您应该指定目标字段实例和可访问性强制标志(如果您使用非公共字段)。更多信息你可以在这里找到。
假设没有SecurityManager阻止你这样做,你可以使用setAccessible绕过private并重置修饰符来摆脱final,并实际修改一个私有静态final字段。
这里有一个例子:
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");
modifiersField.setAccessible(true);
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"
}
}
假设没有抛出SecurityException,上面的代码输出“Everything is true”。
这里的实际操作如下:
main中的基元布尔值true和false被自动装箱以引用类型布尔“常量”布尔。TRUE和布尔值。假 反射用于更改公共静态final布尔值。FALSE表示由Boolean引用的布尔值。真正的 因此,随后每当一个false被自动装箱为布尔值。FALSE,它与Boolean引用的布尔值相同。真正的 以前的“假”现在都是“真”了
相关问题
使用反射更改静态最终文件。用于单元测试的separatorChar 如何限制setAccessible仅“合法”使用? 有搞乱整数的缓存的例子,突变字符串等
警告
无论何时你做这样的事情都应该非常小心。它可能不起作用,因为可能存在SecurityManager,但即使它没有,根据使用模式,它也可能不起作用。
JLS 17.5.3 Subsequent Modification of Final Fields In some cases, such as deserialization, the system will need to change the final fields of an object after construction. final fields can be changed via reflection and other implementation dependent means. The only pattern in which this has reasonable semantics is one in which an object is constructed and then the final fields of the object are updated. The object should not be made visible to other threads, nor should the final fields be read, until all updates to the final fields of the object are complete. Freezes of a final field occur both at the end of the constructor in which the final field is set, and immediately after each modification of a final field via reflection or other special mechanism. Even then, there are a number of complications. If a final field is initialized to a compile-time constant in the field declaration, changes to the final field may not be observed, since uses of that final field are replaced at compile time with the compile-time constant. Another problem is that the specification allows aggressive optimization of final fields. Within a thread, it is permissible to reorder reads of a final field with those modifications of a final field that do not take place in the constructor.
另请参阅
常量表达式 这种技术不太可能与原始私有静态final布尔值一起工作,因为它是可内联的编译时常量,因此“new”值可能无法观察到
附录:关于按位操作
从本质上讲,
field.getModifiers() & ~Modifier.FINAL
关闭与Modifier对应的位。FINAL from field.getModifiers()。&是位补,~是位补。
另请参阅
维基百科/按位操作
记住常数表达式
还是不能解决这个问题?会像我一样陷入抑郁吗?您的代码是这样的吗?
public class A {
private final String myVar = "Some Value";
}
阅读这个答案的评论,特别是@Pshemo的评论,它提醒我常量表达式的处理方式不同,所以不可能修改它。因此,你需要修改你的代码,看起来像这样:
public class A {
private final String myVar;
private A() {
myVar = "Some Value";
}
}
如果你不是类的所有者…我懂你!
要了解更多关于这种行为的细节,请阅读这个?
在存在安全管理器的情况下,可以使用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;
});
final字段的全部意义在于一旦设置它就不能重新分配。JVM使用这个保证来维护各个地方的一致性(例如内部类引用外部变量)。所以没有。如果能够这样做,就会破坏JVM!
解决办法不是一开始就宣布它是最终的。
从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;
}
更多细节请参见这篇文章。