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

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

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

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

当前回答

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

其他回答

刚刚在一个面试问题上看到了这个问题,如果可能的话,在反射或运行时改变最终变量。 我真的很感兴趣,所以我变成了:

 /**
 * @author Dmitrijs Lobanovskis
 * @since 03/03/2016.
 */
public class SomeClass {

    private final String str;

    SomeClass(){
        this.str = "This is the string that never changes!";
    }

    public String getStr() {
        return str;
    }

    @Override
    public String toString() {
        return "Class name: " + getClass() + " Value: " + getStr();
    }
}

一些带有final String变量的简单类。在主类中 进口java.lang.reflect.Field;

/**
 * @author Dmitrijs Lobanovskis
 * @since 03/03/2016.
 */
public class Main {


    public static void main(String[] args) throws Exception{

        SomeClass someClass = new SomeClass();
        System.out.println(someClass);

        Field field = someClass.getClass().getDeclaredField("str");
        field.setAccessible(true);

        field.set(someClass, "There you are");

        System.out.println(someClass);
    }
}

输出如下:

Class name: class SomeClass Value: This is the string that never changes!
Class name: class SomeClass Value: There you are

Process finished with exit code 0

根据文档 https://docs.oracle.com/javase/tutorial/reflect/member/fieldValues.html

在部署到JDK 1.8u91之前,接受的答案对我来说是有效的。 然后我意识到它在野外失败了。集(null, newValue);行,当我在调用setFinalStatic方法之前通过反射读取值。

可能读取导致了Java反射内部的某种不同设置(即失败情况下的sun.reflect.UnsafeQualifiedStaticObjectFieldAccessorImpl,而不是成功情况下的sun.reflect.UnsafeStaticObjectFieldAccessorImpl),但我没有进一步详细说明。

因为我需要在旧值的基础上临时设置新值,然后再将旧值设置回来,所以我对signature做了一点改变,在外部提供计算功能的同时也返回旧值:

public static <T> T assignFinalField(Object object, Class<?> clazz, String fieldName, UnaryOperator<T> newValueFunction) {
    Field f = null, ff = null;
    try {
        f = clazz.getDeclaredField(fieldName);
        final int oldM = f.getModifiers();
        final int newM = oldM & ~Modifier.FINAL;
        ff = Field.class.getDeclaredField("modifiers");
        ff.setAccessible(true);
        ff.setInt(f,newM);
        f.setAccessible(true);

        T result = (T)f.get(object);
        T newValue = newValueFunction.apply(result);

        f.set(object,newValue);
        ff.setInt(f,oldM);

        return result;
    } ...

然而,对于一般情况,这是不够的。

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

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

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

我还集成了joor库

只使用

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

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

如果你的字段是私有的,你可以这样做:

MyClass myClass= new MyClass();
Field aField= myClass.getClass().getDeclaredField("someField");
aField.setAccessible(true);
aField.set(myClass, "newValueForAString");

和抛出/处理NoSuchFieldException