我有一个带有私有静态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);
当前回答
除了排名靠前的答案,你还可以使用最简单的方法。Apache公共的FieldUtils类已经有特定的方法可以做这些事情。请看看FieldUtils。removeFinalModifier方法。您应该指定目标字段实例和可访问性强制标志(如果您使用非公共字段)。更多信息你可以在这里找到。
其他回答
在JDK 18中,这将不再可能,因为作为JEP-416 (PR)的一部分,通过invokedynamic和MethodHandles重新实现了核心反射。
下面是Mandy Chung的评论,她是这本不可思议的作品的主要作者。重点是我的。
如果底层字段为final,则field对象具有写访问权限当且仅当 setAccessible(true)已成功用于此Field对象; 电场是非静态的;而且 字段的声明类不是一个隐藏类;而且 字段的声明类不是记录类。
从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;
}
更多细节请参见这篇文章。
即使是final字段,也可以在静态初始化器之外修改,并且(至少JVM HotSpot)将完美地执行字节码。
问题是Java编译器不允许这样做,但是可以使用objectweb.asm轻松绕过。从JVM规范的角度来看,这是一个无效的类文件,但它通过了字节码验证,然后在JVM HotSpot OpenJDK12下成功加载和初始化:
ClassWriter cw = new ClassWriter(0);
cw.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, "Cl", null, "java/lang/Object", null);
{
FieldVisitor fv = cw.visitField(Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC | Opcodes.ACC_FINAL, "fld", "I", null, null);
fv.visitEnd();
}
{
// public void setFinalField1() { //... }
MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, "setFinalField1", "()V", null, null);
mv.visitMaxs(2, 1);
mv.visitInsn(Opcodes.ICONST_5);
mv.visitFieldInsn(Opcodes.PUTSTATIC, "Cl", "fld", "I");
mv.visitInsn(Opcodes.RETURN);
mv.visitEnd();
}
{
// public void setFinalField2() { //... }
MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, "setFinalField2", "()V", null, null);
mv.visitMaxs(2, 1);
mv.visitInsn(Opcodes.ICONST_2);
mv.visitFieldInsn(Opcodes.PUTSTATIC, "Cl", "fld", "I");
mv.visitInsn(Opcodes.RETURN);
mv.visitEnd();
}
cw.visitEnd();
在Java中,该类大致如下所示:
public class Cl{
private static final int fld;
public static void setFinalField1(){
fld = 5;
}
public static void setFinalField2(){
fld = 2;
}
}
它不能用javac编译,但可以由JVM加载和执行。
JVM HotSpot对这样的类有特殊处理,因为它阻止这样的“常量”参与常量折叠。这个检查是在类初始化的字节码重写阶段完成的:
// Check if any final field of the class given as parameter is modified
// outside of initializer methods of the class. Fields that are modified
// are marked with a flag. For marked fields, the compilers do not perform
// constant folding (as the field can be changed after initialization).
//
// The check is performed after verification and only if verification has
// succeeded. Therefore, the class is guaranteed to be well-formed.
InstanceKlass* klass = method->method_holder();
u2 bc_index = Bytes::get_Java_u2(bcp + prefix_length + 1);
constantPoolHandle cp(method->constants());
Symbol* ref_class_name = cp->klass_name_at(cp->klass_ref_index_at(bc_index));
if (klass->name() == ref_class_name) {
Symbol* field_name = cp->name_ref_at(bc_index);
Symbol* field_sig = cp->signature_ref_at(bc_index);
fieldDescriptor fd;
if (klass->find_field(field_name, field_sig, &fd) != NULL) {
if (fd.access_flags().is_final()) {
if (fd.access_flags().is_static()) {
if (!method->is_static_initializer()) {
fd.set_has_initialized_final_update(true);
}
} else {
if (!method->is_object_initializer()) {
fd.set_has_initialized_final_update(true);
}
}
}
}
}
}
JVM HotSpot检查的唯一限制是final字段不应该在final字段声明所在的类之外修改。
假设没有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";
}
}
如果你不是类的所有者…我懂你!
要了解更多关于这种行为的细节,请阅读这个?
我还集成了joor库
只使用
Reflect.on(yourObject).set("finalFieldName", finalFieldValue);
我还修复了一个覆盖的问题,以前的解决方案似乎错过了。 但是,只有在没有其他好的解决方案时,才要小心使用这种方法。