使用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
中的原始boolean
值true
和false
被自动绑定到引用typesBoolean
“常量”Boolean.TRUE
和Boolean.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或String
types的字段可以是编译时常量。 任何引用该字段的代码中都会内联一个常量。 由于该字段实际上并未在运行时读取,因此更改它将不起作用。
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