小编典典

使用 Java 反射更改私有静态最终字段

all

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

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

有没有办法改变价值?

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

阅读 129

收藏
2022-03-10

共1个答案

小编典典

假设
noSecurityManager阻止您这样做,您可以使用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".

这里实际做了如下:

  • 原始booleantruefalseinmain被自动装箱以引用类型Boolean“常量”Boolean.TRUEBoolean.FALSE
  • 反射是用来改变public static final Boolean.FALSE引用所Boolean引用的Boolean.TRUE
  • 因此,随后每当 afalse被自动装箱到 时Boolean.FALSE,它都指的是BooleanBoolean.TRUE
  • "false"现在的一切都是"true"

注意事项

每当您执行此类操作时,都应格外小心。它可能不起作用,因为
aSecurityManager可能存在,但即使它不存在,取决于使用模式,它可能会或可能不会起作用。

JLS 17.5.3
最终字段的后续修改

在某些情况下,例如反序列化,系统将需要final在构造后更改对象的字段。final可以通过反射和其他依赖于实现的方式更改字段。唯一具有合理语义的模式是构造一个对象,然后final更新对象的字段。final在对象字段的所有更新完成之前,不应使对象对其他线程可见,也不应读取final字段。字段的冻结final发生在设置字段的构造函数的末尾,以及通过反射或其他特殊机制final对字段进行的每次修改之后。final

即便如此,仍有许多并发症。如果final在字段声明中将字段初始化为编译时常量,则final可能不会观察到该字段的更改,因为该final字段的使用在编译时被编译时常量替换。

另一个问题是规范允许积极优化final字段。在线程中,允许final使用未在构造函数中发生的对最终字段的修改对字段的读取进行重新排序。

也可以看看

  • JLS 15.28 常量表达式
    • 这种技术不太可能与原语一起使用private static final boolean,因为它可以作为编译时常量内联,因此“新”值可能无法观察到

附录:关于按位操作

本质上,

field.getModifiers() & ~Modifier.FINAL

关闭对应于Modifier.FINALfrom的位field.getModifiers()&是按位与,并且~是按位补码。

也可以看看


记住常量表达式

仍然无法解决这个问题?像我一样陷入抑郁症吗?你的代码看起来像这样吗?

public class A {
    private final String myVar = "Some Value";
}

阅读对此答案的评论,特别是@Pshemo
的评论,它提醒我常量表达式的处理方式不同,因此
无法 修改它。因此,您需要将代码更改为如下所示:

public class A {
    private final String myVar;

    private A() {
        myVar = "Some Value";
    }
}
2022-03-10