我有一个带有私有静态final字段的类,不幸的是,我需要在运行时更改它。

使用反射我得到这个错误:java.lang.IllegalAccessException:不能设置静态最终布尔字段

有什么方法可以改变这个值吗?

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

当前回答

在JDK 18中,这将不再可能,因为作为JEP-416 (PR)的一部分,通过invokedynamic和MethodHandles重新实现了核心反射。

下面是Mandy Chung的评论,她是这本不可思议的作品的主要作者。重点是我的。

如果底层字段为final,则field对象具有写访问权限当且仅当 setAccessible(true)已成功用于此Field对象; 电场是非静态的;而且 字段的声明类不是一个隐藏类;而且 字段的声明类不是记录类。

其他回答

在JDK 18中,这将不再可能,因为作为JEP-416 (PR)的一部分,通过invokedynamic和MethodHandles重新实现了核心反射。

下面是Mandy Chung的评论,她是这本不可思议的作品的主要作者。重点是我的。

如果底层字段为final,则field对象具有写访问权限当且仅当 setAccessible(true)已成功用于此Field对象; 电场是非静态的;而且 字段的声明类不是一个隐藏类;而且 字段的声明类不是记录类。

如果赋给一个静态final布尔字段的值在编译时是已知的,那么它就是一个常量。原始或原始字段 字符串类型可以是编译时常量。常量将内联到引用该字段的任何代码中。由于字段在运行时并不实际读取,因此更改它将没有任何影响。

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

假设没有SecurityManager阻止你这样做,你可以使用setAccessible绕过private并重置修饰符来摆脱final,并实际修改一个私有静态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”。

这里的实际操作如下:

main中的基元布尔值true和false被自动装箱以引用类型布尔“常量”布尔。TRUE和布尔值。假 反射用于更改公共静态final布尔值。FALSE表示由Boolean引用的布尔值。真正的 因此,随后每当一个false被自动装箱为布尔值。FALSE,它与Boolean引用的布尔值相同。真正的 以前的“假”现在都是“真”了

相关问题

使用反射更改静态最终文件。用于单元测试的separatorChar 如何限制setAccessible仅“合法”使用? 有搞乱整数的缓存的例子,突变字符串等


警告

无论何时你做这样的事情都应该非常小心。它可能不起作用,因为可能存在SecurityManager,但即使它没有,根据使用模式,它也可能不起作用。

JLS 17.5.3 Subsequent Modification of Final Fields In some cases, such as deserialization, the system will need to change the final fields of an object after construction. final fields can be changed via reflection and other implementation dependent means. The only pattern in which this has reasonable semantics is one in which an object is constructed and then the final fields of the object are updated. The object should not be made visible to other threads, nor should the final fields be read, until all updates to the final fields of the object are complete. Freezes of a final field occur both at the end of the constructor in which the final field is set, and immediately after each modification of a final field via reflection or other special mechanism. Even then, there are a number of complications. If a final field is initialized to a compile-time constant in the field declaration, changes to the final field may not be observed, since uses of that final field are replaced at compile time with the compile-time constant. Another problem is that the specification allows aggressive optimization of final fields. Within a thread, it is permissible to reorder reads of a final field with those modifications of a final field that do not take place in the constructor.

另请参阅

常量表达式 这种技术不太可能与原始私有静态final布尔值一起工作,因为它是可内联的编译时常量,因此“new”值可能无法观察到


附录:关于按位操作

从本质上讲,

field.getModifiers() & ~Modifier.FINAL

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

另请参阅

维基百科/按位操作


记住常数表达式

还是不能解决这个问题?会像我一样陷入抑郁吗?您的代码是这样的吗?

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

阅读这个答案的评论,特别是@Pshemo的评论,它提醒我常量表达式的处理方式不同,所以不可能修改它。因此,你需要修改你的代码,看起来像这样:

public class A {
    private final String myVar;

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

如果你不是类的所有者…我懂你!

要了解更多关于这种行为的细节,请阅读这个?

我还集成了joor库

只使用

      Reflect.on(yourObject).set("finalFieldName", finalFieldValue);

我还修复了一个覆盖的问题,以前的解决方案似乎错过了。 但是,只有在没有其他好的解决方案时,才要小心使用这种方法。

在存在安全管理器的情况下,可以使用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;
});