我有一个带有 private static final
字段的类,不幸的是,我需要在运行时更改它。
使用反射我得到这个错误:java.lang.IllegalAccessException: Can not set static final boolean field
有没有办法改变价值?
Field hack = WarpTransform2D.class.getDeclaredField("USE_HACK");
hack.setAccessible(true);
hack.set(null, true);
System.out/in/err
是如此“特别”,以至于 Java 内存模型不得不特别提及它们。它们不是应该遵循的例子。
假设没有 SecurityManager
阻止您这样做,您可以使用 setAccessible
绕过 private
并重置修饰符以摆脱 final
,并实际修改 private static 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 自动装箱以引用类型布尔“常量” Boolean.TRUE 和 Boolean.FALSE
反射用于将 public static final Boolean.FALSE 更改为引用 Boolean.TRUE 所引用的布尔值
因此,随后每当一个 false 自动装箱为 Boolean.FALSE,它引用的布尔值与 Boolean.TRUE 所引用的布尔值相同
一切“假”现在都是“真”
相关问题
使用反射更改静态最终 File.separatorChar 以进行单元测试
如何将 setAccessible 限制为仅“合法”使用?有弄乱整数缓存、改变字符串等的例子
有弄乱整数缓存、改变字符串等的例子
注意事项
每当您执行此类操作时,都应格外小心。它可能不起作用,因为可能存在 SecurityManager
,但即使它不存在,取决于使用模式,它可能会或可能不会起作用。
JLS 17.5.3 最终字段的后续修改在某些情况下,例如反序列化,系统将需要在构造后更改对象的最终字段。 final 字段可以通过反射和其他依赖于实现的方式进行更改。唯一具有合理语义的模式是构造对象,然后更新对象的最终字段。在对象的 final 字段的所有更新完成之前,不应使该对象对其他线程可见,也不应读取 final 字段。最终字段的冻结发生在设置了最终字段的构造函数的末尾,以及在每次通过反射或其他特殊机制修改最终字段之后立即发生。即便如此,仍有许多并发症。如果在字段声明中将 final 字段初始化为编译时常量,则可能不会观察到对 final 字段的更改,因为该 final 字段的使用在编译时被编译时常量替换。另一个问题是规范允许对最终字段进行积极优化。在一个线程中,允许使用未在构造函数中发生的对最终字段的修改来重新排序对最终字段的读取。
也可以看看
JLS 15.28 常量表达式 此技术不太可能与原始私有静态最终布尔值一起使用,因为它可以作为编译时常量内联,因此“新”值可能无法观察到
这种技术不太可能与原始私有静态最终布尔值一起使用,因为它可以作为编译时常量内联,因此“新”值可能无法观察到
附录:关于按位操作
本质上,
field.getModifiers() & ~Modifier.FINAL
从 field.getModifiers()
关闭对应于 Modifier.FINAL
的位。 &
是按位与,~
是按位补码。
也可以看看
维基百科/按位运算
记住常量表达式
仍然无法解决这个问题?像我一样陷入抑郁症吗?你的代码看起来像这样吗?
public class A {
private final String myVar = "Some Value";
}
阅读对此答案的评论,特别是 @Pshemo 的评论,它提醒我 Constant Expressions 的处理方式不同,因此不可能对其进行修改。因此,您需要将代码更改为如下所示:
public class A {
private final String myVar;
private A() {
myVar = "Some Value";
}
}
如果你不是班级的主人……我感觉到你了!
有关为什么会出现这种行为的更多详细信息 read this?
如果分配给 static final boolean
字段的值在编译时已知,则它是一个常量。原始或 String
类型的字段可以是编译时常量。常量将内联在任何引用该字段的代码中。由于在运行时实际上并未读取该字段,因此更改它将无效。
Java language specification 表示:
如果一个字段是一个常量变量(第 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
public static final Boolean FALSE = new Boolean(false)
不是 public static final boolean FALSE = false
Java语言规范第17章第17.5.4节“写保护字段”有点好奇:
通常,不能修改最终和静态的字段。但是,System.in、System.out 和 System.err 是静态 final 字段,由于遗留原因,必须允许通过方法 System.setIn、System.setOut 和 System.setErr 进行更改。我们将这些字段称为写保护以将它们与普通的 final 字段区分开来。
来源:http://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html#jls-17.5.4
我还将它与 joor library
只需使用
Reflect.on(yourObject).set("finalFieldName", finalFieldValue);
我还修复了以前的解决方案似乎遗漏的 override
问题。但是要非常小心地使用它,只有在没有其他好的解决方案时才能使用。
除了排名靠前的答案,您还可以使用最简单的方法。 Apache commons FieldUtils
类已经有可以做这些事情的特定方法。请看一下 FieldUtils.removeFinalModifier
方法。您应该指定目标字段实例和可访问性强制标志(如果您使用非公共字段)。您可以找到更多信息 here。
java.lang.UnsupportedOperationException: In java 12+ final cannot be removed.
即使是 final
,也可以在静态初始化程序之外修改字段,并且(至少 JVM HotSpot)将完美地执行字节码。
问题是 Java 编译器不允许这样做,但可以使用 objectweb.asm
轻松绕过。这是 p̶e̶r̶f̶e̶c̶t̶l̶y̶̶v̶a̶l̶i̶d̶̶c̶l̶a̶s̶s̶f̶i̶l̶e̶ 从 JVMS 规范的角度来看是一个无效的类文件,但它通过了字节码验证,然后在 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 对此类类进行了特殊处理,因为它防止此类“常量”参与常量折叠。此检查在 bytecode rewriting phase of class initialization 上完成:
// 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
字段。
EVIL
和美丽。
如果存在安全管理员,则可以使用 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 18 中,这将不再可能,因为在 invokedynamic
和 MethodHandle
上重新实现了核心反射作为 JEP-416 (PR) 的一部分。
在 following comment 中引用 Mandy Chung - 谁是这部令人难以置信的作品的主要作者。重点是我的。
如果基础字段是最终字段,则当且仅当此字段对象的 setAccessible(true) 成功时,字段对象才具有写入权限;该字段是非静态的;并且该字段的声明类不是隐藏类;并且该字段的声明类不是记录类。
刚刚在一个面试问题上看到了这个问题,如果可能的话,可以通过反射或在运行时更改最终变量。真的很感兴趣,所以我变成了:
/**
* @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();
}
}
一些带有最终字符串变量的简单类。所以在主类中 import 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
static
final 字段,因此此代码不起作用。 setAccessible(true)
仅适用于设置最终实例字段。
接受的答案对我有用,直到部署在 JDK 1.8u91 上。然后,当我在调用 setFinalStatic
方法之前通过反射读取值时,我意识到它在 field.set(null, newValue);
行失败了。
可能读取导致 Java 反射内部的不同设置(即失败情况下的 sun.reflect.UnsafeQualifiedStaticObjectFieldAccessorImpl
而不是成功情况下的 sun.reflect.UnsafeStaticObjectFieldAccessorImpl
),但我没有进一步详细说明。
由于我需要根据旧值临时设置新值,然后再设置旧值,我稍微更改了签名以提供外部计算功能并返回旧值:
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;
} ...
但是,对于一般情况,这还不够。
这里的许多答案都很有用,但我发现它们都不适用于 Android
,尤其是。我什至是 joor 的 Reflect
的一个相当大的用户,而且它和 apache 的 FieldUtils
都不是——在一些答案中都提到过,做诀窍。
安卓的问题
之所以如此,根本原因是在 Android 上,Field
类中没有 modifiers
字段,这会使涉及此代码的任何建议(如标记的答案中所示)无用:
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");
所以,答案是否定的……
解决方案
很简单 - 字段名称不是 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)。
java.lang.NoSuchFieldException: No field modifiers in class Ljava/lang/reflect/Field; (declaration of 'java.lang.reflect.Field' appears in /apex/com.android.runtime/javalib/core-oj.jar)
。建议的解决方案适用于我的情况。 (这个错误目前只有一个谷歌结果,所以希望人们现在能找到这个页面)
从 Java 12 开始,给出的答案将不起作用。
下面是一个示例,说明如何修改自 Java 12 以来的 private static final
字段(基于 this answer)。
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;
}
有关详细信息,请参阅 this thread。
--illegal-access=permit
,@JohannesKuhn 就可以工作
--illegal-access=permit
在 Java 17 中被移除。
static final
字段。 That never really worked。它可能看起来有效,但最终 - 依赖这种效果会使您的代码变得不可移植。
如果您的字段只是私有的,您可以这样做:
MyClass myClass= new MyClass();
Field aField= myClass.getClass().getDeclaredField("someField");
aField.setAccessible(true);
aField.set(myClass, "newValueForAString");
并抛出/处理 NoSuchFieldException
final
字段的全部意义在于它一旦设置就无法重新分配。 JVM 使用这个保证来保持不同地方的一致性(例如,内部类引用外部变量)。所以不行。能够这样做会破坏JVM!
解决方案不是首先声明它final
。
final
在多线程执行中具有特殊作用 - 更改 final
值也会破坏 Java 内存模型。
final
的字段不应声明为 static
。
getDeclaredField()
而不是getField()
作为目标类final String myConstant = "x";
之类的内容但会失败的人:请记住,编译时常量将由编译器内联,因此当您编写像System.out.println(myConstant);
这样的代码时,它将被编译为System.out.println("x");
,因为编译器知道常量的值在编译时。要摆脱这个问题,您需要在运行时初始化常量,例如final String myConstant = new String("x");
。同样对于像final int myField = 11
这样的原语,使用final int myField = new Integer(11);
或final Integer myField = 11;