java final set_使用Java反射更改私有静态final字段

使用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

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值