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

新手上路,请多包涵

我有一个带有 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);

原文由 fixitagain 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 1.2k
2 个回答

Assuming no SecurityManager is preventing you from doing this, you can use setAccessible to get around private and resetting the modifier to get rid of 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"

这里实际做了什么如下:

  • The primitive boolean values true and false in main are autoboxed to reference type Boolean “constants” Boolean.TRUEBoolean.FALSE
  • 反射用于更改 public static final Boolean.FALSE 引用 Boolean 引用 Boolean.TRUE
  • As a result, subsequently whenever a false is autoboxed to Boolean.FALSE , it refers to the same Boolean as the one refered to by Boolean.TRUE
  • 以前的一切 "false" 现在是 "true"

相关问题


注意事项

每当你做这样的事情时,都应该格外小心。它可能不起作用,因为 SecurityManager 可能存在,但即使它不存在,根据使用模式,它也可能起作用,也可能不起作用。

JLS 17.5.3 final 字段的后续修改

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

即使那样,也有许多并发症。如果在字段声明 final 字段初始化为编译时常量,则可能无法观察到 final 字段的更改,因为使用了 final 字段在编译时被编译时常量替换。

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

也可以看看

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

附录:关于按位操作

本质上,

 field.getModifiers() & ~Modifier.FINAL

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

也可以看看


记住常量表达式

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

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

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

 public class A {
    private final String myVar;

    private A() {
        myVar = "Some Value";
    }
}

如果你不是班级的主人……我觉得你!

有关此行为的原因的更多详细信息, 请阅读此 内容?

原文由 polygenelubricants 发布,翻译遵循 CC BY-SA 4.0 许可协议

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

Java 语言规范 是这样说的:

如果字段是常量变量(§4.12.4),则删除关键字 final 或更改其值不会导致它们不运行而破坏与预先存在的二进制文件的兼容性,但 它们不会看到任何新的用法值除非重新编译它们。 即使用法本身不是编译时常量表达式(§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

原文由 erickson 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题