使用Javareflection更改私有静态最终字段

我有一个private static final字段的类,不幸的是,我需要在运行时更改。

使用reflection我得到这个错误: java.lang.IllegalAccessException: Can not set static final boolean field

有没有办法改变价值?

 Field hack = WarpTransform2D.class.getDeclaredField("USE_HACK"); hack.setAccessible(true); hack.set(null, true); 

假设没有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中的原始booleantruefalse被自动绑定到引用typesBoolean “常量” Boolean.TRUEBoolean.FALSE
  • reflection用于改变public static final Boolean.FALSE来引用由Boolean.TRUE引用的Boolean
  • 结果,随后每当false被自动复制到Boolean.FALSE ,它就会引用与Boolean.FALSE引用的相同的Boolean
  • 所有现在"false"东西都是"true"

相关问题

  • 使用reflection更改static final File.separatorChar进行unit testing
  • 如何限制setAccessible只有“合法”的用途?
    • 有一些与Integer的caching混乱的例子,改变一个String ,等等

注意事项

当你做这样的事情时,应该非常小心。 它可能无法正常工作,因为SecurityManager可能存在,但即使不存在,根据使用模式,它可能会也可能不会工作。

JLS 17.5.3最终字段的后续修改

在某些情况下,如反序列化,系统将需要在构build后更改对象的final字段。 final字段可以通过reflection和其他实现相关的手段来改变。 唯一具有合理语义的模式是在其中构build对象,然后更新对象的final字段。 该对象不应该让其他线程可见,也不应该读取final字段,直到对对象的final字段的所有更新都完成。 final字段的冻结发生在final字段被设置的构造函数的末尾,并且在通过reflection或其他特殊机制每次修改final字段之后立即出现。

即使如此,还有一些并发症。 如果final字段在字段声明中被初始化为编译时常量,则可能不会观察到对final字段的更改,因为在编译时将该final字段的使用replace为编译时常量。

另一个问题是规范允许final字段的积极优化。 在一个线程中,允许对final字段的读取进行重新sorting,这些修改不会在构造函数中发生。

也可以看看

  • JLS 15.28常量expression式
    • 这种技术不太可能与原始的private static final boolean ,因为它可以作为编译时常量进行内联,因此“新”值可能不可观察

附录:关于按位操作

从本质上讲,

 field.getModifiers() & ~Modifier.FINAL 

closuresfield.getModifiers()对应于Modifier.FINAL的位。 &是按位和,而是是按位补码。

也可以看看

  • 维基百科/按位操作

如果分配给static final boolean字段的值在编译时已知,则它是一个常量。 原始types或Stringtypes的字段可以是编译时常量。 任何引用该字段的代码中都会内联一个常量。 由于该字段实际上并未在运行时读取,因此更改它将不起作用。

Java语言规范说:

如果一个字段是一个常量variables(§4.12.4),那么删除关键字final或者改变它的值不会破坏与预先存在的二进制文件的兼容性,通过使它们不运行,但是它们将不会看到任何新的用法值除非他们被重新编译。 即使使用本身不是编译时常量expression式(第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 

来自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

我也把它与joor库集成在一起

只是使用

  Reflect.on(yourObject).setFinal("finalFieldName", finalFieldValue); 

此外,我解决了以前的解决scheme似乎错过override问题。 但是,只有在没有其他好的解决scheme的时候,才能使用它。

在存在安全pipe理器的情况下,可以使用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" } } 

在lambdaexpression式中, AccessController.doPrivileged ,可以简化为:

 AccessController.doPrivileged((PrivilegedAction) () -> { modifiersField.setAccessible(true); return null; }); 

随着排名最高的答案,你可能会使用一些简单的方法。 Apache公共的FieldUtils类已经有特定的方法可以做的东西。 请看看FieldUtils.removeFinalModifier方法。 您应该指定目标字段实例和辅助function强制标志(如果您使用非公开字段)。 更多信息你可以在这里find。

接受的答案为我工作,直到部署在JDK 1.8u91。 然后我意识到它在field.set(null, newValue);失败field.set(null, newValue); 当我调用setFinalStatic方法之前通过reflection读取了值。

读取可能会导致Javareflection内部设置的不同设置(即失败情况下的sun.reflect.UnsafeStaticObjectFieldAccessorImpl ,而不是成功情况下的sun.reflect.UnsafeStaticObjectFieldAccessorImpl ),但我没有进一步详细说明。

由于我需要基于旧值临时设置新的值,并将旧的值设置回来,所以我改变了一点点签名以提供外部计算function,并返回以前的值:

 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; } ... 

但是,一般情况下这是不够的。

final领域的重点是一旦设定就不能重新分配。 JVM使用这种保证来保持各个地方的一致性(例如引用外部variables的内部类)。 所以不行。 能够这样做会打破JVM!

解决办法不是首先宣布它是final的。

刚刚在面试问题的一个问题上看到了这个问题,如果可能的话用reflection或运行时改变最终的variables。 真的有兴趣,所以我成为了:

  /** * @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(); } } 

一些简单的类与最终的stringvariables。 所以在主类中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