我有一个带有私有静态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);
当前回答
如果你的字段是私有的,你可以这样做:
MyClass myClass= new MyClass();
Field aField= myClass.getClass().getDeclaredField("someField");
aField.setAccessible(true);
aField.set(myClass, "newValueForAString");
和抛出/处理NoSuchFieldException
其他回答
在存在安全管理器的情况下,可以使用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;
});
如果你的字段是私有的,你可以这样做:
MyClass myClass= new MyClass();
Field aField= myClass.getClass().getDeclaredField("someField");
aField.setAccessible(true);
aField.set(myClass, "newValueForAString");
和抛出/处理NoSuchFieldException
Java语言规范,第17章,第17.5.4节“写保护字段”:
通常,final和static字段不能被修改。 然而,系统。在系统。out和System。Err是静态的最终字段 由于遗留的原因,必须允许方法对其进行更改 系统。开始,系统。setOut和System.setErr。我们提到这些 字段被写入保护,以区别于普通字段 最后一个字段。
来源:http://docs.oracle.com/javase/specs/jls/se7/html/jls - 17. # jls-17.5.4 html
如果赋给一个静态final布尔字段的值在编译时是已知的,那么它就是一个常量。原始或原始字段 字符串类型可以是编译时常量。常量将内联到引用该字段的任何代码中。由于字段在运行时并不实际读取,因此更改它将没有任何影响。
Java语言规范是这样说的:
如果一个场是常数变量 (§4.12.4),然后删除关键字 Final或改变其值将不会 打破与既存状况的兼容性 通过让二进制文件不运行, 但他们不会看到任何新的价值 对于字段的使用,除非他们 重新编译。这是真的,即使 用法本身不是编译时 常量表达式(§15.28)
这里有一个例子:
class Flag {
static final boolean FLAG = true;
}
class Checker {
public static void main(String... argv) {
System.out.println(Flag.FLAG);
}
}
如果你反编译Checker,你会看到它而不是引用Flag。FLAG,代码只是将一个值1 (true)压入堆栈(指令#3)。
0: getstatic #2; //Field java/lang/System.out:Ljava/io/PrintStream;
3: iconst_1
4: invokevirtual #3; //Method java/io/PrintStream.println:(Z)V
7: return
在部署到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;
} ...
然而,对于一般情况,这是不够的。