使用Java反射更改私有静态final字段

fix*_*ain 454 java reflection static final private

我有一个带有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);
Run Code Online (Sandbox Code Playgroud)

pol*_*nts 851

假设没有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"
   }
}
Run Code Online (Sandbox Code Playgroud)

假设没有SecurityException抛出,上面的代码打印出来"Everything is true".

这里实际做的是如下:

  • 原始booleantruefalsein main被自动装箱以引用类型Boolean"常量" Boolean.TRUEBoolean.FALSE
  • 反射用于更改public static final Boolean.FALSE引用所Boolean引用的Boolean.TRUE
  • 结果,随后每当a false被自动装箱时Boolean.FALSE,它指的是与Boolean所提到的相同Boolean.TRUE
  • "false"现在的一切都是"true"

相关问题


注意事项

每当你做这样的事情时,都应该格外小心.它可能不起作用,因为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,因为它可以作为编译时常量内联,因此"新"值可能无法被观察到

附录:关于按位操作

实质上,

field.getModifiers() & ~Modifier.FINAL
Run Code Online (Sandbox Code Playgroud)

关断对应于比特Modifier.FINALfield.getModifiers().&是按位 - 和,~是按位补码.

也可以看看


记住常量表达式

仍然无法解决这个问题?已经像我一样陷入了萧条?你的代码看起来像这样吗?

public class A {
    private final String myVar = "Some Value";
}
Run Code Online (Sandbox Code Playgroud)

阅读这个答案的评论,特别是@Pshemo的评论,它提醒我,Constant表达式处理不同,所以不可能修改它.因此,您需要将代码更改为如下所示:

public class A {
    private final String myVar;

    private A() {
        myVar = "Some Value";
    }
}
Run Code Online (Sandbox Code Playgroud)

如果你不是班上的老板......我觉得你!

有关此行为为什么读取此内容的详细信息?

  • 哟,dawg.我听说你喜欢反射,所以我在场上反思,这样你就可以反思. (57认同)
  • @thecoop,@ HalfBrian:毫无疑问,这是_EXTREMELY EVIL_,但这个例子是由设计选择的.我的回答只显示在某些情况下这是可能的.我能想到的最令人作呕的例子是刻意选择,希望也许人们会立即反感,而不是爱上这种技巧. (37认同)
  • @mgaert确实如此,但你必须使用`getDeclaredField()`而不是`getField()`来实现目标类 (14认同)
  • 请注意,Boolean.FALSE不是**私有.这真的适用于"私有最终静态"成员吗? (9认同)
  • +1.对于那些试图改变像`final String myConstant ="x";`并且会失败的人:记住编译时间常量将被编译器内联,所以当你编写像System.out.println(myConstant)这样的代码时; `它将被编译为`System.out.println("x");`因为编译器在编译时知道常量的值.要摆脱这个问题,你需要在运行时初始化你的常量,如`final String myConstant = new String("x");`.同样在原语如`final int myField = 11`的情况下使用`final int myField = new Integer(11);`或`final Integer myField = 11;` (9认同)
  • 当我看到这个:`System.out.format("Everything is%s",false); //"一切都是真的""我以为你和我们搞砸了,然后我读了你的其余代码,但是它带来了一个可读性问题,你接受的一些事情是真的(或者在这种情况下是假的). (5认同)
  • 没关系,看看Android源代码,我发现,"modifiers"字段在Android上是"accessFlags". (5认同)
  • 在 Java 12 中不再允许这样做。参见错误 8210496 https://bugs.java.com/bugdatabase/view_bug.do?bug_id=8210496 https://hg.openjdk.java.net/jdk-updates/jdk12u/rev/f55a4bc91ef4 (5认同)
  • 请注意,您可以事先检查`SecurityManager`以查看是否允许这样做:`final SecurityManager sm = System.getSecurityManager(); if(sm!= null){sm.checkPermission(new java.lang.reflect.ReflectPermission("suppressAccessChecks")); }` (4认同)
  • 请注意,JLS §17.5.3 指的是非“静态”“最终”字段。根本无法预见对“静态”“最终”字段的后续修改,因此完全不符合规范。任何事情都可能发生,包括完全没有影响或 JVM 完全崩溃…… (3认同)
  • 另一个警告是,您不能在删除修饰符之前读取该字段,否则它会被缓存:/sf/ask/3437205481/ (3认同)
  • 我想知道这在 JDK 17 上如何工作。我认为“修饰符”不再可用。 (3认同)
  • (再次)请注意,“JLS 17.5.3 最终字段的后续修改”仅适用于*实例字段*。它描述了当“构造后更改对象的final字段”时可能发生的问题,而规范甚至没有预见到在类初始化后更改“staticfinal”字段。这就是为什么对于非静态的“final”字段,一个简单的“setAccessible(true)”就足以更改它,而更改“static final”字段则需要更深层次的修改,而 JDK 开发人员会尝试禁用这些修改。即使您找到另一个解决方法,它也会破坏下一个版本。 (2认同)
  • @swpalmer 是的,不再在 Java 12 中工作,请检查[这个新答案](/sf/answers/3923027671/)技巧 (2认同)

eri*_*son 54

如果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);
  }
}
Run Code Online (Sandbox Code Playgroud)

如果您反编译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
Run Code Online (Sandbox Code Playgroud)

  • @Bill K - 在polygenlubricants的回答中,该字段不是编译时常量.它是`public static final Boolean FALSE = new Boolean(false)`not`public static final boolean FALSE = false (25认同)
  • @Bill K - 不,这不是指JIT编译.依赖类文件实际上将包含内联值,并且不包含对独立类的引用.这是一个非常简单的测试实验; 我将添加一个例子. (3认同)

小智 13

从Java语言规范第17章第17.5.4节"写保护字段"中获得一点好奇心:

通常,可能不会修改最终和静态字段.但是,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


iir*_*ekm 6

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

只是用

      Reflect.on(yourObject).set("finalFieldName", finalFieldValue);
Run Code Online (Sandbox Code Playgroud)

我还override解决了以前的解决方案似乎错过的问题.但是,只有在没有其他好的解决方案时才非常小心地使用它.

  • 当我尝试这个(JDK12)时,我得到一个异常:“无法设置最终 ___ 字段”。 (2认同)
  • @AaronIba Java 12+ 中不再允许。 (2认同)

Van*_*gaS 6

如果存在安全管理器,则可以利用 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"
    }
}
Run Code Online (Sandbox Code Playgroud)

在lambda表达式中AccessController.doPrivileged,可以简化为:

AccessController.doPrivileged((PrivilegedAction) () -> {
    modifiersField.setAccessible(true);
    return null;
});
Run Code Online (Sandbox Code Playgroud)


Som*_*ame 6

即使final一个字段可以在静态初始值设定项之外修改,并且(至少 JVM HotSpot)将完美地执行字节码。

问题是 Java 编译器不允许这样做,但是可以使用objectweb.asm. 这是 p?e?r?f?e?c?t?l?y? ?有效的??c?l?a?s?s?f?i?l?e? 从 JVMS 规范的角度来看一个无效的类文件,但它通过了字节码验证,然后在 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();
Run Code Online (Sandbox Code Playgroud)

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

public class Cl{
    private static final int fld;

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

    public static void setFinalField2(){
        fld = 2;
    }
}
Run Code Online (Sandbox Code Playgroud)

不能用 编译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);
            }
          }
        }
      }
    }
}
Run Code Online (Sandbox Code Playgroud)

JVM HotSpot 检查的唯一限制是final不应在final声明该字段的类之外修改该字段。

  • 我不同意“完全有效的类文件”。[JVMS §6.5](https://docs.oracle.com/javase/specs/jvms/se15/html/jvms-6.html#jvms-6.5.putstatic) 明确指出:“否则,如果解析的字段是Final,它必须在当前类或接口中声明,**并且该指令必须出现在当前类或接口的类或接口初始化方法**中。否则,会抛出 IllegalAccessError”。所以这只是另一种情况,实现公然违反规范,并且代码分布在多个地方来处理应该被拒绝的内容 (4认同)
  • 这就是纯粹的“邪恶”和美丽。 (2认同)

nnd*_*dru 5

除了排名靠前的答案,您可以使用一些最简单的方法.Apache commons FieldUtils类已经有了可以完成这些工作的特殊方法.请看一下FieldUtils.removeFinalModifier方法.您应该指定目标字段实例和辅助功能强制标记(如果您使用非公共字段).您可以在此处找到更多信息.

  • 是吗?复制一种方法听起来比导入整个库更简单(这与您要复制的方法具有相同的作用)。 (3认同)
  • 在 Java 12+ 中不起作用:“java.lang.UnsupportedOperationException:在 Java 12+ 中无法删除final。” (2认同)

pri*_*ngi 5

从Java 12开始,给出的答案将不起作用。

这是一个关于如何private static finalJava 12开始修改字段的示例(基于此答案)。

  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;
  }
Run Code Online (Sandbox Code Playgroud)

请参阅此线程了解更多详细信息。