我有一个带有私有静态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);
当前回答
假设没有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";
}
}
如果你不是类的所有者…我懂你!
要了解更多关于这种行为的细节,请阅读这个?
其他回答
除了排名靠前的答案,你还可以使用最简单的方法。Apache公共的FieldUtils类已经有特定的方法可以做这些事情。请看看FieldUtils。removeFinalModifier方法。您应该指定目标字段实例和可访问性强制标志(如果您使用非公共字段)。更多信息你可以在这里找到。
从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;
}
更多细节请参见这篇文章。
如果你的字段是私有的,你可以这样做:
MyClass myClass= new MyClass();
Field aField= myClass.getClass().getDeclaredField("someField");
aField.setAccessible(true);
aField.set(myClass, "newValueForAString");
和抛出/处理NoSuchFieldException
刚刚在一个面试问题上看到了这个问题,如果可能的话,在反射或运行时改变最终变量。 我真的很感兴趣,所以我变成了:
/**
* @author Dmitrijs Lobanovskis
* @since 03/03/2016.
*/
public class SomeClass {
private final String str;
SomeClass(){
this.str = "This is the string that never changes!";
}
public String getStr() {
return str;
}
@Override
public String toString() {
return "Class name: " + getClass() + " Value: " + getStr();
}
}
一些带有final String变量的简单类。在主类中 进口java.lang.reflect.Field;
/**
* @author Dmitrijs Lobanovskis
* @since 03/03/2016.
*/
public class Main {
public static void main(String[] args) throws Exception{
SomeClass someClass = new SomeClass();
System.out.println(someClass);
Field field = someClass.getClass().getDeclaredField("str");
field.setAccessible(true);
field.set(someClass, "There you are");
System.out.println(someClass);
}
}
输出如下:
Class name: class SomeClass Value: This is the string that never changes!
Class name: class SomeClass Value: There you are
Process finished with exit code 0
根据文档 https://docs.oracle.com/javase/tutorial/reflect/member/fieldValues.html
在部署到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;
} ...
然而,对于一般情况,这是不够的。