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

2022-08-31 04:20:41

我有一个带有字段的类,不幸的是,我需要在运行时更改它。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);

答案 1

假设 no 阻止您执行此操作,您可以使用 绕过并重置修饰符以摆脱 ,并实际修改字段。SecurityManagersetAccessibleprivatefinalprivate 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"

这里实际执行的操作如下:

  • 基元值和 in 会自动装箱以引用类型“常量”和booleantruefalsemainBooleanBoolean.TRUEBoolean.FALSE
  • 反射用于更改公共静态最终布尔值。FALSE 引用BooleanBoolean.TRUE
  • 因此,随后每当 a 被自动装箱到 时,它所指的与 所指的相同falseBoolean.FALSEBooleanBoolean.TRUE
  • 现在的一切都是"false""true"

相关问题


警告

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

JLS 17.5.3 最终字段的后续修改

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

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

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

另请参见

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

附录:关于按位操作

本质上

field.getModifiers() & ~Modifier.FINAL

关闭 与 相对应的位。 是按位和,并且是按位补码。Modifier.FINALfield.getModifiers()&~

另请参见


记住常量表达式

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

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

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

public class A {
    private final String myVar;

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

如果您不是该课程的所有者...我感觉到你了!

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


答案 2

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

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);
  }
}

如果你反编译,你会看到代码只是将值1()推到堆栈上(指令#3),而不是引用。CheckerFlag.FLAGtrue

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

推荐