使用 Java 反射更改私有静态最终字段
我有一个带有 private static final
字段的类,不幸的是,我需要在运行时更改它。
使用反射我收到此错误:java.lang.IllegalAccessException:无法设置静态最终布尔字段
有什么方法可以更改该值吗?
Field hack = WarpTransform2D.class.getDeclaredField("USE_HACK");
hack.setAccessible(true);
hack.set(null, true);
I have a class with a private static final
field that, unfortunately, I need to change it at run-time.
Using reflection I get this error: java.lang.IllegalAccessException: Can not set static final boolean field
Is there any way to change the value?
Field hack = WarpTransform2D.class.getDeclaredField("USE_HACK");
hack.setAccessible(true);
hack.set(null, true);
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(17)
final
字段的全部要点是一旦设置就无法重新分配。 JVM 使用此保证来维护各个位置的一致性(例如引用外部变量的内部类)。所以不。如果这样做就会破坏 JVM!解决方案不是首先将其声明为
final
。The whole point of a
final
field is that it cannot be reassigned once set. The JVM uses this guarentee to maintain consistency in various places (eg inner classes referencing outer variables). So no. Being able to do so would break the JVM!The solution is not to declare it
final
in the first place.假设没有
SecurityManager
阻止您执行此操作,您可以使用setAccessible
绕过private
并重置修饰符以摆脱Final
,并实际修改一个private static final
字段。下面是一个示例:
假设没有抛出
SecurityException
,上面的代码将打印“Everything is true”
。这里实际完成的操作如下:
main
中的原始boolean
值true
和false
被自动装箱为引用类型Boolean
“常量”Boolean.TRUE
和Boolean.FALSE
公共静态最终 Boolean.FALSE
引用由Boolean.TRUE
引用的Boolean
false
都会自动装箱为Boolean.FALSE< /code>,它引用的
Boolean
与Boolean.TRUE
引用的Boolean
相同,"false"
现在都是“true”
相关问题
static final File.separatorChar
以进行单元测试Integer
的缓存、改变String
等的示例注意事项
每当你做这样的事情时都应该格外小心。它可能不起作用,因为可能存在
SecurityManager
,但即使它不存在,根据使用模式,它可能会也可能不会起作用。另请参阅
私有静态最终布尔值
一起使用,因为它可以作为编译时常量内联,因此“新”值可能无法观察到附录:关于按位操作
本质上,
变成从
field.getModifiers()
中删除与Modifier.FINAL
对应的位。&
是按位与,~
是按位补。另请参阅
记住常量表达式
仍然无法解决这个问题?像我一样抑郁症吗?你的代码看起来像这样吗?
阅读这个答案的评论,特别是@Pshemo的评论,它提醒我 常量表达式的处理方式不同,因此不可能修改它。因此,您需要将代码更改为如下所示:
如果您不是该类的所有者...我感觉到您!
有关此行为原因的更多详细信息阅读此内容?
Assuming no
SecurityManager
is preventing you from doing this, you can usesetAccessible
to get aroundprivate
and resetting the modifier to get rid offinal
, and actually modify aprivate static final
field.Here's an example:
Assuming no
SecurityException
is thrown, the above code prints"Everything is true"
.What's actually done here is as follows:
boolean
valuestrue
andfalse
inmain
are autoboxed to reference typeBoolean
"constants"Boolean.TRUE
andBoolean.FALSE
public static final Boolean.FALSE
to refer to theBoolean
referred to byBoolean.TRUE
false
is autoboxed toBoolean.FALSE
, it refers to the sameBoolean
as the one refered to byBoolean.TRUE
"false"
now is"true"
Related questions
static final File.separatorChar
for unit testingInteger
's cache, mutating aString
, etcCaveats
Extreme care should be taken whenever you do something like this. It may not work because a
SecurityManager
may be present, but even if it doesn't, depending on usage pattern, it may or may not work.See also
private static final boolean
, because it's inlineable as a compile-time constant and thus the "new" value may not be observableAppendix: On the bitwise manipulation
Essentially,
turns off the bit corresponding to
Modifier.FINAL
fromfield.getModifiers()
.&
is the bitwise-and, and~
is the bitwise-complement.See also
Remember Constant Expressions
Still not being able to solve this?, have fallen onto depression like I did for it? Does your code looks like this?
Reading the comments on this answer, specially the one by @Pshemo, it reminded me that Constant Expressions are handled different so it will be impossible to modify it. Hence you will need to change your code to look like this:
if you are not the owner of the class... I feel you!
For more details about why this behavior read this?
如果分配给
static final boolean
字段的值在编译时已知,则它是一个常量。原始或的字段String
类型可以是编译时常量。常量将内联到引用该字段的任何代码中。由于该字段实际上并未在运行时读取,因此更改它不会产生任何效果。Java 语言规范 是这样说的:
下面是一个示例:
如果反编译
Checker
,您会发现代码只是推送值 1 (true
) 入栈(指令#3)。If the value assigned to a
static final boolean
field is known at compile-time, it is a constant. Fields of primitive orString
type can be compile-time constants. A constant will be inlined in any code that references the field. Since the field is not actually read at runtime, changing it then will have no effect.The Java language specification says this:
Here's an example:
If you decompile
Checker
, you'll see that instead of referencingFlag.FLAG
, the code simply pushes a value of 1 (true
) onto the stack (instruction #3).Java 语言规范第 17 章第 17.5.4 节“写保护字段”有点令人好奇:
来源:http://docs.oracle.com/ javase/specs/jls/se7/html/jls-17.html#jls-17.5.4
A little curiosity from the Java Language Specification, chapter 17, section 17.5.4 "Write-protected Fields":
Source: http://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html#jls-17.5.4
我还将它与 joor 库集成,
只需使用
我还修复了
override
的问题以前的解决方案似乎忽略了这一点。然而,只有当没有其他好的解决方案时,才要非常小心地使用它。
I also integrated it with joor library
Just use
Also I fixed an issue with
override
which the previous solutions seem to miss.However use this very carefully, only when there's no other good solution.
除了排名最高的答案之外,您还可以使用最简单的方法。 Apache commons
FieldUtils
类已经具有可以完成这些操作的特定方法。请看一下FieldUtils.removeFinalModifier
方法。您应该指定目标字段实例和可访问性强制标志(如果您使用非公共字段)。您可以在此处找到更多信息< /a>.Along with top ranked answer you may use a bit simpliest approach. Apache commons
FieldUtils
class already has particular method that can do the stuff. Please, take a look atFieldUtils.removeFinalModifier
method. You should specify target field instance and accessibility forcing flag (if you play with non-public fields). More info you can find here.如果存在安全管理器,则可以使用
AccessController.doPrivileged
从上面接受的答案中获取相同的示例:
在 lambda 表达式中,
AccessController.doPrivileged
可以是简化为:In case of presence of a Security Manager, one can make use of
AccessController.doPrivileged
Taking the same example from accepted answer above:
In lambda expression,
AccessController.doPrivileged
, can be simplified to:即使是
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 下成功加载并初始化: ,大致如下所示:
该类 不能用
javac
编译,但可以被JVM加载并执行。JVM HotSpot 对此类类进行了特殊处理,防止此类“常量”参与常量折叠。此检查是在 类初始化的字节码重写阶段:
JVM HotSpot 检查的唯一限制是
final
字段不应在final
所在的类之外进行修改字段声明于。Even in spite of being
final
a field can be modified outside of static initializer and (at least JVM HotSpot) will execute the bytecode perfectly fine.The problem is that Java compiler does not allow this, but this can be easily bypassed using
objectweb.asm
. Here is p̶e̶r̶f̶e̶c̶t̶l̶y̶ ̶v̶a̶l̶i̶d̶ ̶c̶l̶a̶s̶s̶f̶i̶l̶e̶ an invalid classfile from the JVMS specification standpoint, but it passes bytecode verification and then is successfully loaded and initialized under JVM HotSpot OpenJDK12:In Java, the class looks roughly speaking as follows:
which cannot be compiled with
javac
, but can be loaded and executed by JVM.JVM HotSpot has special treatment of such classes in the sense that it prevents such "constants" from participating in constant folding. This check is done on the bytecode rewriting phase of class initialization:
The only restriction that JVM HotSpot checks is that the
final
field should not be modified outside of the class that thefinal
field is declared at.从 Java 12 开始,给出的答案将不起作用。
下面是一个关于如何从 Java 12 开始修改
private static final
字段的示例(基于 这个答案)。请参阅此线程了解更多细节。
Since Java 12 onwards, the answers given will not work.
Here is an example on how to modify a
private static final
field since Java 12 (based on this answer).See this thread for more details.
在 JDK 18 中,这将不再可能,因为作为 JEP-416 (公关)。
Mandy Chung 的引述——他是这部令人难以置信的作品的主要作者——在 以下评论。重点是我的。
With JDK 18 this won't be possible anymore due to the reimplementation of the core reflection over
invokedynamic
andMethodHandle
s as part of JEP-416 (PR).Quote of Mandy Chung – who is the main author of this incredible work – in the following comment. Emphasis are mine.
这里的许多答案都很有用,但我发现它们都不适用于
Android
,特别是。我什至是 joor 的Reflect
的忠实用户,而且它和 apache 的FieldUtils
都不是 -两者都在一些答案中提到过,可以解决问题。Android 的问题
出现这种情况的根本原因是因为在 Android 上,
Field
类中没有modifiers
字段,该字段会呈现涉及此代码的任何建议(如标记的答案中所示) ),无用:事实上,引用
FieldUtils.removeFinalModifier()
的话:所以,答案是否定的...
解决方案
很简单 - 字段名称而不是
modifiers
是accessFlags
。这可以解决问题:Many of the answers here are useful, but I've found none of them to work on
Android
, in particular. I'm even a pretty hefty user ofReflect
by joor, and neither it nor apache'sFieldUtils
- both mentioned here in some of the answers, do the trick.Problem with Android
The fundamental reason why this is so is because on Android there's no
modifiers
field in theField
class, which renders any suggestion involving this code (as in the marked answer), useless:In fact, to quote from
FieldUtils.removeFinalModifier()
:So, the answer is no...
Solution
Pretty easy - instead of
modifiers
, the field name isaccessFlags
. This does the trick:刚刚在面试问题之一上看到了这个问题,如果可能的话,可以通过反射或在运行时更改最终变量。
真的很感兴趣,所以我变成了:
一些带有最终字符串变量的简单类。所以在主类中
导入 java.lang.reflect.Field;
输出如下:
根据文档
https://docs.oracle.com/javase/tutorial/reflect/member /fieldValues.html
Just saw that question on one of the interview question, if possible to change final variable with reflection or in runtime.
Got really interested, so that what I became with:
Some simple class with final String variable. So in the main class
import java.lang.reflect.Field;
The output will be as follows:
According to documentation
https://docs.oracle.com/javase/tutorial/reflect/member/fieldValues.html
在部署到 JDK 1.8u91 上之前,接受的答案对我一直有效。
然后我意识到,当我在调用
setFinalStatic
方法之前通过反射读取值时,它在field.set(null, newValue);
行失败。可能读取导致了 Java 反射内部设置的不同(即失败情况下的
sun.reflect.UnsafeQualifiedStaticObjectFieldAccessorImpl
而不是成功情况下的sun.reflect.UnsafeStaticObjectFieldAccessorImpl
),但我没有不进一步详细说明。由于我需要根据旧值临时设置新值,然后再设置回旧值,因此我稍微更改了签名以在外部提供计算函数并返回旧值:
但是对于一般情况,这还不够。
The accepted answer worked for me until deployed on JDK 1.8u91.
Then I realized it failed at
field.set(null, newValue);
line when I had read the value via reflection before calling ofsetFinalStatic
method.Probably the read caused somehow different setup of Java reflection internals (namely
sun.reflect.UnsafeQualifiedStaticObjectFieldAccessorImpl
in failing case instead ofsun.reflect.UnsafeStaticObjectFieldAccessorImpl
in success case) but I didn't elaborate it further.Since I needed to temporarily set new value based on old value and later set old value back, I changed signature little bit to provide computation function externally and also return old value:
However for general case this would not be sufficient.
要使其适用于 JDK 21,您可以使用选项
-Djdk.reflect.useDirectMethodHandle=false
使 JDK 11 - 17 可访问
To make this worked with JDK 21 you can use option
-Djdk.reflect.useDirectMethodHandle=false
Make accessable for JDK 11 - 17
如果您的字段只是私有的,您可以这样做:
并抛出/处理 NoSuchFieldException
If your field is simply private you can do this:
and throw/handle NoSuchFieldException
评分最高的答案不适用于 Java 21 中使用 MethodHandles 和忽略 Field 抽象对象上的标志值。
一种解决方案是使用 Unsafe,但是使用 this JEP Unsafe 和重要的
long objectFieldOffset(Field f) 和
long staticFieldOffset(Field f)
方法已被弃用并被删除,因此,这在将来将不起作用:我不推荐这样做,但在 Java 21 中,在进行繁重的工作时,可以使用新的反射实现如果确实需要,可以使用内部 API。
没有
Unsafe
的 Java 21+ 解决方案其要点是使用
MethodHandle
,它可以通过从内部getDirectFieldNoSecurityManager(.. .)
的 Lookup 方法,通过向其提供一个MemberName
来通过反射进行操作以从中删除最终标志。您需要添加以下 JVM 选项才能使其正常工作:
然后您可以
请注意,当设置内联时,这不适用于纯字符串文字和原始值(例如
static final String = "str"
)请参阅我的回答此处,了解有关使用内部 API 在 Java 21 中设置 Final 字段的初步尝试和想法没有不安全。
The top rated answer does not work with the new Reflection implementation of JEP416 in e.g. Java 21 that uses MethodHandles and ignores the flags value on the Field abstraction object.
One solution is to use Unsafe, however with this JEP Unsafe and the important
long objectFieldOffset(Field f)
andlong staticFieldOffset(Field f)
methods are getting deprecated for removal so for example this will not work in the future:I do not recommend this but it is possible in Java 21 with the new reflection implementation when making heavy use of the internal API if really needed.
Java 21+ solution without
Unsafe
The gist of it is to use a
MethodHandle
that can write to a static final field by getting it from the internalgetDirectFieldNoSecurityManager(...)
method of the Lookup by providing it with aMemberName
that is manipulated via Reflection to remove the final flag from it.You will need to add the following JVM options for this to work:
And then you can
Please note that this will not work for pure String literals and primitive values when set inline (e.g.
static final String = "str"
)See my answer here for a the initial attempt and thoughts regarding using the internal API to set a final field in Java 21 without Unsafe.
在 openjdk17 中,我不仅要从
Field.modifiers
中删除final
修饰符,还要从Field.root.modifiers
中删除。Field.root
在field.set(...)
内部使用 此处。所以我的代码是这样的:
In openjdk17 I had to remove
final
modifier not just fromField.modifiers
, but also fromField.root.modifiers
.Field.root
is used internally infield.set(...)
here.So my code is this: