我有一个带有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楼

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

通常,不得修改final和static字段。 但是,System.in,System.out和System.err是静态的最终字段,由于遗留原因,必须允许使用System.setIn,System.setOut和System.setErr方法进行更改。 我们称这些字段为写保护的,以区别于普通的最终字段。

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


#2楼

我也将它与joor库集成在一起

只需使用

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

我还解决了以前的解决方案似乎override问题。 但是,只有在没有其他好的解决方案时,才应谨慎使用此功能。


#3楼

final字段的全部要点是,一旦设置,就不能重新分配它。 JVM使用此保证人在各个地方保持一致性(例如,内部类引用外部变量)。 所以不行。 这样做会破坏JVM!

解决方案是首先不要将其声明为final 。


#4楼

假设没有SecurityManager阻止您执行此操作,则可以使用setAccessible避开private并重置修饰符以摆脱final ,并实际上修改private 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" 。

实际执行的操作如下:

  • main中的原始boolean值true和false装箱以引用类型Boolean “ constants” Boolean.TRUE和Boolean.FALSE
  • 反思是用来改变public static final Boolean.FALSE来指代Boolean被称为Boolean.TRUE
  • 其结果是,随后每当false被autoboxed到Boolean.FALSE ,它指的是相同的Boolean作为一个由refered到Boolean.TRUE
  • 现在所有"false"为"true"

相关问题

  • 使用反射更改static final File.separatorChar以进行单元测试
  • 如何将setAccessible限制为仅“合法”使用?
  • 有使Integer的缓存混乱,使String突变等的示例

注意事项

每当您执行此类操作时,都应格外小心。 由于可能存在SecurityManager因此它可能不起作用,但是即使不起作用,根据使用模式的不同,它也可能起作用或不起作用。

JLS 17.5.3最终字段的后续修改

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

即使这样,仍然存在许多并发症。 如果final字段被初始化为在字段声明一个编译时间常数,改变到final字段可以不被观察到,因为该用途final场是在编译时与编译时间常数替代。

另一个问题是,该规范允许对final字段进行积极的优化。 在线程内,可以使用在构造函数中不进行的对最终字段的修改来重新排序final字段的读取。

也可以看看

  • JLS 15.28常数表达式
  • 此技术不太可能与原始的private static final boolean ,因为它可以作为编译时常量内联,因此“ new”值可能无法观察到

附录:关于按位操作

实质上,

field.getModifiers() & ~Modifier.FINAL

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

也可以看看

  • 维基百科/按位操作

记住常数表达式

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

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

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

public class A {
    private final String myVar;

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

如果您不是课程的所有者,我会感觉到您!

有关为什么此行为的更多详细信息,请阅读此内容 ?


#5楼

如果在编译时知道分配给static final boolean字段的值,则它是一个常数。 原始类型或String类型的字段可以是编译时常量。 在引用该字段的任何代码中都将内联一个常量。 由于在运行时实际上并未读取该字段,因此对其进行更改将无效。

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

#6楼

只是在面试问题中看到了一个问题,如果可能的话,可以通过反射或在运行时更改最终变量。 真的很感兴趣,所以我开始变得:

/**
 * @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();
    }
}

一些带有最终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


#7楼

如果存在安全管理器,则可以使用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;
});

#8楼

在将其部署到JDK 1.8u91上之前,可接受的答案一直对我有用。 然后我意识到它在field.set(null, newValue);失败了field.set(null, newValue); 在调用setFinalStatic方法之前通过反射读取值的setFinalStatic 。

也许读引起的Java反射内部有些不同设置(即sun.reflect.UnsafeQualifiedStaticObjectFieldAccessorImpl中,而不是失败的情况下sun.reflect.UnsafeStaticObjectFieldAccessorImpl在成功的情况下),但我并没有进一步阐述它。

由于我需要根据旧值临时设置新值,然后再将旧值重新设置,因此我做了一点点改动以在外部提供计算功能,并返回旧值:

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

但是,对于一般情况,这还不够。


#9楼

除了排名靠前的答案,您还可以使用一些简单的方法。 Apache commons FieldUtils类已经具有可以执行此操作的特定方法。 请看看FieldUtils.removeFinalModifier方法。 您应该指定目标字段实例和可访问性强制标志(如果您使用非公共字段)。 您可以在此处找到更多信息。


#10楼

如果您的字段只是私有的,则可以执行以下操作:

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

并抛出/处理NoSuchFieldException


#11楼

即使是final字段,也可以在静态初始化程序之外修改字段,并且(至少JVM HotSpot)可以完美执行字节码。

问题是Java编译器不允许这样做,但是可以使用objectweb.asm轻松绕过objectweb.asm 。 这是通过字节码验证并在JVM HotSpot OpenJDK12下成功加载和初始化的完全有效的类文件:

ClassWriter cw = new ClassWriter(0);
cw.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, "Cl", null, "java/lang/Object", null);
{
    FieldVisitor fv = cw.visitField(Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC | Opcodes.ACC_FINAL, "fld", "I", null, null);
    fv.visitEnd();
}
{
    // public void setFinalField1() { //... }
    MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, "setFinalField1", "()V", null, null);
    mv.visitMaxs(2, 1);
    mv.visitInsn(Opcodes.ICONST_5);
    mv.visitFieldInsn(Opcodes.PUTSTATIC, "Cl", "fld", "I");
    mv.visitInsn(Opcodes.RETURN);
    mv.visitEnd();
}
{
    // public void setFinalField2() { //... }
    MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, "setFinalField2", "()V", null, null);
    mv.visitMaxs(2, 1);
    mv.visitInsn(Opcodes.ICONST_2);
    mv.visitFieldInsn(Opcodes.PUTSTATIC, "Cl", "fld", "I");
    mv.visitInsn(Opcodes.RETURN);
    mv.visitEnd();
}
cw.visitEnd();

在Java中,该类看起来大致如下:

public class Cl{
    private static final int fld;

    public static void setFinalField1(){
        fld = 5;
    }

    public static void setFinalField2(){
        fld = 2;
    }
}

不能使用javac进行编译,但是可以由JVM加载和执行。

JVM HotSpot对此类有特殊的对待,因为它可以防止此类“常量”参与常量折叠。 该检查是在类初始化的字节码重写阶段完成的 :

// Check if any final field of the class given as parameter is modified
// outside of initializer methods of the class. Fields that are modified
// are marked with a flag. For marked fields, the compilers do not perform
// constant folding (as the field can be changed after initialization).
//
// The check is performed after verification and only if verification has
// succeeded. Therefore, the class is guaranteed to be well-formed.
InstanceKlass* klass = method->method_holder();
u2 bc_index = Bytes::get_Java_u2(bcp + prefix_length + 1);
constantPoolHandle cp(method->constants());
Symbol* ref_class_name = cp->klass_name_at(cp->klass_ref_index_at(bc_index));
if (klass->name() == ref_class_name) {
   Symbol* field_name = cp->name_ref_at(bc_index);
   Symbol* field_sig = cp->signature_ref_at(bc_index);

   fieldDescriptor fd;
   if (klass->find_field(field_name, field_sig, &fd) != NULL) {
      if (fd.access_flags().is_final()) {
         if (fd.access_flags().is_static()) {
            if (!method->is_static_initializer()) {
               fd.set_has_initialized_final_update(true);
            }
          } else {
            if (!method->is_object_initializer()) {
              fd.set_has_initialized_final_update(true);
            }
          }
        }
      }
    }
}

唯一的限制是JVM HotSpot的检查是在final领域不应该修改的是外部类final场在声明。