我有一个带有私有静态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 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;
    } ...

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

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

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

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

假设没有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";
    }
}

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

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

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

 /**
 * @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