使用Java反射更改私有静态final字段
我有一个java.lang.IllegalAccessException: Can not set static final boolean field字段的课程,不幸的是,我需要在运行时更改。
使用反射我得到这个错误:java.lang.IllegalAccessException: Can not set static final boolean field
有没有办法改变价值?
Field hack = WarpTransform2D.class.getDeclaredField("USE_HACK");
hack.setAccessible(true);
hack.set(null, true);
9个解决方案
752 votes
假设没有Modifier.FINAL阻止你这样做,你可以使用field.getModifiers()来解决&并重置修改器以摆脱~,并实际修改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"
}
}
假设没有抛出Modifier.FINAL,上面的代码打印field.getModifiers()。
这里实际做的是如下:
原始Modifier.FINAL值field.getModifiers()和&在~中自动编码为参考类型final“常量”final和final
反射用于更改Modifier.FINAL以参考Modifier.FINAL由field.getModifiers()提及
因此,随后每当Modifier.FINAL被自动装箱到field.getModifiers()时,它指的是与~相同的&
Modifier.FINAL的全部内容现在是field.getModifiers()
相关问题
使用反射更改Modifier.FINAL进行单元测试
如何将setAccessible限制为仅仅“合法”使用?有乱码Modifier.FINAL的缓存,变异field.getModifiers()等的例子
注意事项
每当你做这样的事情时,都应该格外小心。 它可能不起作用,因为Modifier.FINAL可能存在,但即使它不存在,取决于使用模式,它可能或可能不起作用。
JLS 17.5.3最终字段的后续修改
在某些情况下,例如反序列化,系统将需要在构造之后更改对象的Modifier.FINAL字段。 field.getModifiers()字段可以通过反射和其他依赖于实现的方式进行更改。 它具有合理语义的唯一模式是构造对象然后更新对象的&字段的模式。 对象不应该对其他线程可见,也不应该读取~字段,直到对象的final字段的所有更新都完成为止。 在设置final字段的构造函数的末尾以及通过反射或其他特殊机制每次修改final字段后立即发生冻结final字段。
即使这样,也有许多并发症。 如果在字段声明中将Modifier.FINAL字段初始化为编译时常量,则可能无法观察到对field.getModifiers()字段的更改,因为在编译时将使用该&字段替换编译时常量。
另一个问题是该规范允许积极优化Modifier.FINAL字段。 在一个线程中,允许使用构造函数中不发生的最终字段的那些修改来重新排序field.getModifiers()字段的读取。
也可以看看
JLS 15.28常量表达式这种技术不太可能适用于原语Modifier.FINAL,因为它可以作为编译时常量进行内联,因此“新”值可能无法被观察到
附录:关于按位操作
实质上,
field.getModifiers() & ~Modifier.FINAL
从field.getModifiers()关闭对应于Modifier.FINAL的位.&是按位 - 和,~是按位补码。
也可以看看
维基百科/按位操作
记住常量表达式
仍然无法解决这个问题?,就像我为此做的那样陷入了萧条? 你的代码看起来像这样吗?
public class A {
private final String myVar = "Some Value";
}
阅读这个答案的评论,特别是@Pshemo的评论,它提醒我,Constant Expression的处理方式不同,因此无法对其进行修改。 因此,您需要将代码更改为如下所示:
public class A {
private final String myVar;
private A() {
myVar = "Some Value";
}
}
如果你不是班上的老板......我觉得你!
有关此行为为什么读取此内容的详细信息?
polygenelubricants answered 2019-01-12T05:33:09Z
48 votes
如果在编译时已知分配给Checker字段的值,则它是常量。 原始或的领域Flag.FLAG类型可以是编译时常量。 在引用该字段的任何代码中都会内联一个常量。 由于该字段实际上并未在运行时读取,因此更改它将不起作用。
Java语言规范说:
如果字段是常量变量 (§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(true)推送到堆栈(指令#3)而不是引用Flag.FLAG。
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
erickson answered 2019-01-12T05:34:05Z
12 votes
从Java语言规范第17章第17.5.4节“写保护字段”中获得一点好奇心:
通常,可能不会修改最终和静态字段。 但是,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]
Stephan Markwalder answered 2019-01-12T05:34:41Z
5 votes
我还将它与joor库集成在一起
只是用
Reflect.on(yourObject).set("finalFieldName", finalFieldValue);
此外,我修复了override的问题,以前的解决方案似乎错过了。但是,只有在没有其他好的解决方案时才非常谨慎地使用它。
iirekm answered 2019-01-12T05:35:12Z
4 votes
如果存在安全管理器,可以使用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;
});
VanagaS answered 2019-01-12T05:35:48Z
4 votes
除了排名靠前的答案,您可以使用最简单的方法。 Apache commons FieldUtils类已经有了可以做的东西的特定方法。 请看一下FieldUtils.removeFinalModifier的方法。 您应该指定目标字段实例和辅助功能强制标记(如果您使用非公共字段)。 您可以在此处找到更多信息。
nndru answered 2019-01-12T05:36:11Z
2 votes
接受的答案对我有用,直到部署在JDK 1.8u91上。然后我意识到它在sun.reflect.UnsafeQualifiedStaticObjectFieldAccessorImpl行失败了,当我在调用sun.reflect.UnsafeStaticObjectFieldAccessorImpl方法之前通过反射读取了值。
可能读取导致Java反射内部的某些不同设置(即在失败的情况下为sun.reflect.UnsafeQualifiedStaticObjectFieldAccessorImpl,而在成功的情况下为sun.reflect.UnsafeStaticObjectFieldAccessorImpl),但我没有进一步详细说明。
由于我需要根据旧值临时设置新值,然后将旧值设置回来,我改变了签名位以外部提供计算功能并返回旧值:
public static T assignFinalField(Object object, Class> clazz, String fieldName, UnaryOperator 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;
} ...
但是对于一般情况,这是不够的。
Tomáš Záluský answered 2019-01-12T05:36:54Z
-1 votes
刚刚在一个面试问题上看到了这个问题,如果可能的话,用反射或在运行时改变最终变量。真的很感兴趣,所以我成为了:
/**
* @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]
hasskell answered 2019-01-12T05:37:36Z
-5 votes
final字段的重点是一旦设置就无法重新分配。 JVM使用此保证来维护各个位置的一致性(例如,引用外部变量的内部类)。 所以不行。 能够这样做会破坏JVM!
解决方案不是首先声明它final。
thecoop answered 2019-01-12T05:38:05Z