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

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

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

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

当前回答

这里的许多答案都很有用,但我发现没有一个在Android上特别适用。我甚至是joor的Reflect的忠实用户,无论是它还是apache的FieldUtils——在这里的一些答案中都提到了这一点。

Android的问题

这样做的根本原因是,在Android上,field类中没有修饰符字段,这使得任何涉及这段代码的建议(如在标记的答案中)都是无用的:

Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);

事实上,引用FieldUtils.removeFinalModifier():

// Do all JREs implement Field with a private ivar called "modifiers"?
final Field modifiersField = Field.class.getDeclaredField("modifiers");

所以,答案是否定的……

解决方案

非常简单——字段名是accessFlags,而不是修饰符。这招很管用:

Field accessFlagsField = Field.class.getDeclaredField("accessFlags");
accessFlagsField.setAccessible(true);
accessFlagsField.setInt(field, field.getModifiers() & ~Modifier.FINAL);

旁注#1:无论字段在类中是否是静态的,这都可以工作。

旁注#2:鉴于字段本身可能是私有的,建议也启用对字段本身的访问,使用field. setaccessible (true)(除了accessFlagsField.setAccessible(true))。

其他回答

final字段的全部意义在于一旦设置它就不能重新分配。JVM使用这个保证来维护各个地方的一致性(例如内部类引用外部变量)。所以没有。如果能够这样做,就会破坏JVM!

解决办法不是一开始就宣布它是最终的。

从Java 12开始,给出的答案将不能工作。

下面是一个关于如何修改Java 12以来的私有静态final字段的示例(基于这个答案)。

  private Object modifyField(Object newFieldValue, String fieldName, Object classInstance) throws NoSuchFieldException, IllegalAccessException {
    Field field = classInstance.getClass().getDeclaredField(fieldName);
    VarHandle MODIFIERS;

    field.setAccessible(true);

    var lookup = MethodHandles.privateLookupIn(Field.class, MethodHandles.lookup());
    MODIFIERS = lookup.findVarHandle(Field.class, "modifiers", int.class);
    int mods = field.getModifiers();

    if (Modifier.isFinal(mods)) {
      MODIFIERS.set(field, mods & ~Modifier.FINAL);
    }

    Object previousValue = field.get(classInstance);
    field.set(null, newFieldValue);

    return previousValue;
  }

更多细节请参见这篇文章。

Java语言规范,第17章,第17.5.4节“写保护字段”:

通常,final和static字段不能被修改。 然而,系统。在系统。out和System。Err是静态的最终字段 由于遗留的原因,必须允许方法对其进行更改 系统。开始,系统。setOut和System.setErr。我们提到这些 字段被写入保护,以区别于普通字段 最后一个字段。

来源:http://docs.oracle.com/javase/specs/jls/se7/html/jls - 17. # jls-17.5.4 html

除了排名靠前的答案,你还可以使用最简单的方法。Apache公共的FieldUtils类已经有特定的方法可以做这些事情。请看看FieldUtils。removeFinalModifier方法。您应该指定目标字段实例和可访问性强制标志(如果您使用非公共字段)。更多信息你可以在这里找到。

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