我有一个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);
答案 0 :(得分:803)
假设没有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"
。
这里实际完成的内容如下:
boolean
中的原始true
值false
和main
被自动设置为引用类型Boolean
“常量”Boolean.TRUE
和{{1 }} public static final Boolean.FALSE
以引用Boolean.FALSE
Boolean
Boolean.TRUE
自动装箱到false
时,它就会引用与Boolean.FALSE
Boolean
相同的Boolean.TRUE
"false"
的所有内容都是"true"
static final File.separatorChar
for unit testing Integer
缓存,变异String
等的示例每当你做这样的事情时都要特别小心。它可能不起作用,因为SecurityManager
可能存在,但即使它不存在,取决于使用模式,它可能也可能不起作用。
JLS 17.5.3 Subsequent Modification of Final Fields
在某些情况下,例如反序列化,系统需要在构造后更改对象的
final
字段。final
字段可以通过反射和其他依赖于实现的方式进行更改。它具有合理语义的唯一模式是构造对象然后更新对象的final
字段的模式。对象不应该对其他线程可见,也不应该读取final
字段,直到对象的final
字段的所有更新都完成为止。冻结final
字段既发生在设置了final
字段的构造函数的末尾,也发生在通过反射或其他特殊机制对final
字段进行每次修改之后。 / p>即使这样,也有许多并发症。如果在字段声明中将
final
字段初始化为编译时常量,则可能无法观察到对final
字段的更改,因为在编译时会替换final
字段的使用使用编译时常量。另一个问题是该规范允许对
final
字段进行积极优化。在一个线程中,允许使用构造函数中不发生的最终字段的那些修改来重新排序final
字段的读取。
private static final boolean
一起使用,因为它可以作为编译时常量内联,因此“新”值可能无法被观察到基本上,
field.getModifiers() & ~Modifier.FINAL
关闭与Modifier.FINAL
field.getModifiers()
对应的位。 &
是按位 - 而且~
是按位补码。
仍然无法解决这个问题?已经像我一样陷入了萧条?你的代码看起来像这样吗?
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?
答案 1 :(得分:50)
如果在编译时已知分配给static final boolean
字段的值,则它是常量。原始字段或
String
类型可以是编译时常量。在引用该字段的任何代码中都会内联一个常量。由于该字段实际上并未在运行时读取,因此更改它将无效。
如果字段是常量变量 (§4.12.4),然后删除关键字 最终或改变其价值不会 打破与先前存在的兼容性 二进制文件导致它们不运行, 但他们不会看到任何新的价值 除非他们使用该字段 重新编译。即使这样也是如此 用法本身不是编译时 常数表达式(§15.28)
以下是一个例子:
class Flag {
static final boolean FLAG = true;
}
class Checker {
public static void main(String... argv) {
System.out.println(Flag.FLAG);
}
}
如果您反编译Checker
,您将看到代码只是将值1(Flag.FLAG
)推送到堆栈(指令#3)而不是引用true
。
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
答案 2 :(得分:13)
Java语言规范第17章第17.5.4节“写保护字段”中有点好奇:
通常,可能不会修改final和static字段。 但是,System.in,System.out和System.err是静态最终字段 由于遗留原因,必须允许通过这些方法进行更改 System.setIn,System.setOut和System.setErr。我们指的是这些 字段被写保护以区别于普通字段 最后的领域。
来源:http://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html#jls-17.5.4
答案 3 :(得分:6)
我还将其与joor library
集成在一起只需使用
Reflect.on(yourObject).set("finalFieldName", finalFieldValue);
此外,我修复了以前的解决方案似乎错过的override
问题。
但是,只有在没有其他好的解决方案时才非常谨慎地使用它。
答案 4 :(得分:5)
如果存在安全管理器,可以使用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;
});
答案 5 :(得分:5)
除了排名靠前的答案,您可以使用最简单的方法。 Apache commons FieldUtils
类已经有了可以完成这些工作的特殊方法。请看一下FieldUtils.removeFinalModifier
方法。您应该指定目标字段实例和辅助功能强制标记(如果您使用非公共字段)。您可以找到更多信息here。
答案 6 :(得分:2)
接受的答案对我有用,直到部署在JDK 1.8u91上。
然后我意识到在调用field.set(null, newValue);
方法之前通过反射读取值时setFinalStatic
行失败了。
可能读取导致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;
} ...
然而,对于一般情况,这还不够。
答案 7 :(得分:0)
如果您的字段只是私有的,则可以执行以下操作:
MyClass myClass= new MyClass();
Field aField= myClass.getClass().getDeclaredField("someField");
aField.setAccessible(true);
aField.set(myClass, "newValueForAString");
并抛出/处理NoSuchFieldException
答案 8 :(得分:0)
即使是final
,也可以在静态初始值设定项之外修改字段,并且(至少JVM HotSpot)可以完美执行字节码。
问题是Java编译器不允许这样做,但是可以使用objectweb.asm
轻松地绕开它。这是通过字节码验证并在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
字段。
答案 9 :(得分:0)
这里的许多答案都很有用,但是我发现其中没有一个特别适合Android
使用。我什至是 joor 的Reflect
的相当大的用户,而它和 apache 的FieldUtils
都不是-两者在此处答案,解决问题。
之所以如此,基本原因是因为在Android中,modifiers
类中没有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");
所以,答案是否定的...
非常简单-字段名称为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)
之外,还可以对字段本身进行访问。
答案 10 :(得分:-1)
刚刚在一个面试问题上看到了这个问题,如果可能的话,用反射或运行时改变最终变量。 真的很感兴趣,所以我的成就:
/**
* @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变量的简单类。所以在主类 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
答案 11 :(得分:-5)
final
字段的重点是一旦设置就无法重新分配。 JVM使用此保证来维护各个位置的一致性(例如,引用外部变量的内部类)。所以不行。能够这样做会破坏JVM!
解决方案不是首先声明它final
。