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

发布于 2024-09-11 00:01:11 字数 308 浏览 4 评论 0原文

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

使用反射我收到此错误:java.lang.IllegalAccessException:无法设置静态最终布尔字段

有什么方法可以更改该值吗?

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

I have a class with a private static final field that, unfortunately, I need to change it at run-time.

Using reflection I get this error: java.lang.IllegalAccessException: Can not set static final boolean field

Is there any way to change the value?

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(17

冰之心 2024-09-18 00:01:12

final 字段的全部要点是一旦设置就无法重新分配。 JVM 使用此保证来维护各个位置的一致性(例如引用外部变量的内部类)。所以不。如果这样做就会破坏 JVM!

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

The whole point of a final field is that it cannot be reassigned once set. The JVM uses this guarentee to maintain consistency in various places (eg inner classes referencing outer variables). So no. Being able to do so would break the JVM!

The solution is not to declare it final in the first place.

随遇而安 2024-09-18 00:01:11

假设没有 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 中的原始 booleantruefalse 被自动装箱为引用类型Boolean “常量” Boolean.TRUEBoolean.FALSE
  • 反射用于更改 公共静态最终 Boolean.FALSE引用由 Boolean.TRUE 引用的 Boolean
  • 因此,随后每当 false 都会自动装箱为 Boolean.FALSE< /code>,它引用的 BooleanBoolean.TRUE 引用的 Boolean 相同,
  • 所有 "false" 现在都是 “true”

相关问题


注意事项

每当你做这样的事情时都应该格外小心。它可能不起作用,因为可能存在 SecurityManager,但即使它不存在,根据使用模式,它可能会也可能不会起作用。

JLS 17.5.3 Final 的后续修改字段

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

即便如此,仍然存在许多并发症。如果 final 字段在字段声明中初始化为编译时常量,则可能无法观察到 final 字段的更改,因为使用了该 final< /code> 字段在编译时被替换为编译时常量。

另一个问题是该规范允许对 final 字段进行积极优化。在线程内,允许对 final 字段的读取进行重新排序,并使用构造函数中未发生的对 Final 字段的修改。

另请参阅

  • JLS 15.28 常量表达式
    • 此技术不太可能与原始私有静态最终布尔值一起使用,因为它可以作为编译时常量内联,因此“新”值可能无法观察到

附录:关于按位操作

本质上,

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

如果您不是该类的所有者...我感觉到您!

有关此行为原因的更多详细信息阅读此内容

Assuming no SecurityManager is preventing you from doing this, you can use setAccessible to get around private and resetting the modifier to get rid of final, and actually modify a private static final field.

Here's an example:

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"
   }
}

Assuming no SecurityException is thrown, the above code prints "Everything is true".

What's actually done here is as follows:

  • The primitive boolean values true and false in main are autoboxed to reference type Boolean "constants" Boolean.TRUE and Boolean.FALSE
  • Reflection is used to change the public static final Boolean.FALSE to refer to the Boolean referred to by Boolean.TRUE
  • As a result, subsequently whenever a false is autoboxed to Boolean.FALSE, it refers to the same Boolean as the one refered to by Boolean.TRUE
  • Everything that was "false" now is "true"

Related questions


Caveats

Extreme care should be taken whenever you do something like this. It may not work because a SecurityManager may be present, but even if it doesn't, depending on usage pattern, it may or may not work.

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.

See also

  • JLS 15.28 Constant Expression
    • It's unlikely that this technique works with a primitive private static final boolean, because it's inlineable as a compile-time constant and thus the "new" value may not be observable

Appendix: On the bitwise manipulation

Essentially,

field.getModifiers() & ~Modifier.FINAL

turns off the bit corresponding to Modifier.FINAL from field.getModifiers(). & is the bitwise-and, and ~ is the bitwise-complement.

See also


Remember Constant Expressions

Still not being able to solve this?, have fallen onto depression like I did for it? Does your code looks like this?

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

Reading the comments on this answer, specially the one by @Pshemo, it reminded me that Constant Expressions are handled different so it will be impossible to modify it. Hence you will need to change your code to look like this:

public class A {
    private final String myVar;

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

if you are not the owner of the class... I feel you!

For more details about why this behavior read this?

肤浅与狂妄 2024-09-18 00:01:11

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

Java 语言规范 是这样说的:

如果字段是常量变量
(§4.12.4),然后删除关键字
最终或改变其值不会
破坏与现有的兼容性
使二进制文件不运行,
他们不会看到任何新的价值
对于该字段的使用,除非他们
被重新编译。
即使
用法本身不是编译时
常量表达式(§15.28)

下面是一个示例:

class Flag {
  static final boolean FLAG = true;
}

class Checker {
  public static void main(String... argv) {
    System.out.println(Flag.FLAG);
  }
}

如果反编译 Checker,您会发现代码只是推送值 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

If the value assigned to a static final boolean field is known at compile-time, it is a constant. Fields of primitive or
String type can be compile-time constants. A constant will be inlined in any code that references the field. Since the field is not actually read at runtime, changing it then will have no effect.

The Java language specification says this:

If a field is a constant variable
(§4.12.4), then deleting the keyword
final or changing its value will not
break compatibility with pre-existing
binaries by causing them not to run,
but they will not see any new value
for the usage of the field unless they
are recompiled.
This is true even if
the usage itself is not a compile-time
constant expression (§15.28)

Here's an example:

class Flag {
  static final boolean FLAG = true;
}

class Checker {
  public static void main(String... argv) {
    System.out.println(Flag.FLAG);
  }
}

If you decompile Checker, you'll see that instead of referencing Flag.FLAG, the code simply pushes a value of 1 (true) onto the stack (instruction #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
仄言 2024-09-18 00:01:11

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

A little curiosity from the Java Language Specification, chapter 17, section 17.5.4 "Write-protected Fields":

Normally, a field that is final and static may not be modified.
However, System.in, System.out, and System.err are static final fields
that, for legacy reasons, must be allowed to be changed by the methods
System.setIn, System.setOut, and System.setErr. We refer to these
fields as being write-protected to distinguish them from ordinary
final fields.

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

摇划花蜜的午后 2024-09-18 00:01:11

我还将它与 joor 库集成,

只需使用

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

我还修复了 override 的问题以前的解决方案似乎忽略了这一点。
然而,只有当没有其他好的解决方案时,才要非常小心地使用它。

I also integrated it with joor library

Just use

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

Also I fixed an issue with override which the previous solutions seem to miss.
However use this very carefully, only when there's no other good solution.

鸢与 2024-09-18 00:01:11

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

Along with top ranked answer you may use a bit simpliest approach. Apache commons FieldUtils class already has particular method that can do the stuff. Please, take a look at FieldUtils.removeFinalModifier method. You should specify target field instance and accessibility forcing flag (if you play with non-public fields). More info you can find here.

再可℃爱ぅ一点好了 2024-09-18 00:01:11

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

In case of presence of a Security Manager, one can make use of AccessController.doPrivileged

Taking the same example from accepted answer above:

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"
    }
}

In lambda expression, AccessController.doPrivileged, can be simplified to:

AccessController.doPrivileged((PrivilegedAction) () -> {
    modifiersField.setAccessible(true);
    return null;
});
做个少女永远怀春 2024-09-18 00:01:11

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

问题是 Java 编译器不允许这样做,但是使用 objectweb.asm 可以轻松绕过这一点。这是 p̶e̶r̶f̶e̶c̶t̶l̶y̶ ̶v̶a̶l̶i̶d̶ ̶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();

大致如下所示:

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 所在的类之外进行修改字段声明于。

Even in spite of being final a field can be modified outside of static initializer and (at least JVM HotSpot) will execute the bytecode perfectly fine.

The problem is that Java compiler does not allow this, but this can be easily bypassed using objectweb.asm. Here is p̶e̶r̶f̶e̶c̶t̶l̶y̶ ̶v̶a̶l̶i̶d̶ ̶c̶l̶a̶s̶s̶f̶i̶l̶e̶ an invalid classfile from the JVMS specification standpoint, but it passes bytecode verification and then is successfully loaded and initialized under 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();

In Java, the class looks roughly speaking as follows:

public class Cl{
    private static final int fld;

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

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

which cannot be compiled with javac, but can be loaded and executed by JVM.

JVM HotSpot has special treatment of such classes in the sense that it prevents such "constants" from participating in constant folding. This check is done on the bytecode rewriting phase of class initialization:

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

The only restriction that JVM HotSpot checks is that the final field should not be modified outside of the class that the final field is declared at.

居里长安 2024-09-18 00:01:11

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

下面是一个关于如何从 Java 12 开始修改 private static 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;
  }

请参阅此线程了解更多细节。

Since Java 12 onwards, the answers given will not work.

Here is an example on how to modify a private static final field since Java 12 (based on this answer).

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

See this thread for more details.

旧伤还要旧人安 2024-09-18 00:01:11

在 JDK 18 中,这将不再可能,因为作为 JEP-416 (公关)。

Mandy Chung 的引述——他是这部令人难以置信的作品的主要作者——在 以下评论。重点是我的。

如果基础字段是最终字段,则 Field 对象具有写入权限当且仅当

  • Field 对象的 setAccessible(true) 已成功;
  • 该字段是非静态的;和
  • 字段的声明类不是隐藏类;和
  • 字段的声明类不是记录类。

With JDK 18 this won't be possible anymore due to the reimplementation of the core reflection over invokedynamic and MethodHandles as part of JEP-416 (PR).

Quote of Mandy Chung – who is the main author of this incredible work – in the following comment. Emphasis are mine.

If the underlying field is final, a Field object has write access if and only if

  • setAccessible(true) has succeeded for this Field object;
  • the field is non-static; and
  • the field's declaring class is not a hidden class; and
  • the field's declaring class is not a record class.
迷爱 2024-09-18 00:01:11

这里的许多答案都很有用,但我发现它们都不适用于 Android,特别是。我什至是 joorReflect 的忠实用户,而且它和 apacheFieldUtils 都不是 -两者都在一些答案中提到过,可以解决问题。

Android 的问题

出现这种情况的根本原因是因为在 Android 上,Field 类中没有 modifiers 字段,该字段会呈现涉及此代码的任何建议(如标记的答案中所示) ),无用:

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");

所以,答案是否定的...

解决方案

很简单 - 字段名称而不是 modifiersaccessFlags。这可以解决问题:

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

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

旁注 #2:鉴于字段本身可能是私有的,建议还使用 field.setAccessible(true) (除了 accessFlagsField.setAccessible(true).

Many of the answers here are useful, but I've found none of them to work on Android, in particular. I'm even a pretty hefty user of Reflect by joor, and neither it nor apache's FieldUtils - both mentioned here in some of the answers, do the trick.

Problem with Android

The fundamental reason why this is so is because on Android there's no modifiers field in the Field class, which renders any suggestion involving this code (as in the marked answer), useless:

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

In fact, to quote from FieldUtils.removeFinalModifier():

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

So, the answer is no...

Solution

Pretty easy - instead of modifiers, the field name is accessFlags. This does the trick:

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

Side-note #1: this can work regardless of whether the field is static in the class, or not.

Side-note #2: Seeing that the field itself could be private, it's recommended to also enable access over the field itself, using field.setAccessible(true) (in addition to accessFlagsField.setAccessible(true).

笑饮青盏花 2024-09-18 00:01:11

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

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

一些带有最终字符串变量的简单类。所以在主类中
导入 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

Just saw that question on one of the interview question, if possible to change final variable with reflection or in runtime.
Got really interested, so that what I became with:

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

Some simple class with final String variable. So in the main class
import 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);
    }
}

The output will be as follows:

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

According to documentation
https://docs.oracle.com/javase/tutorial/reflect/member/fieldValues.html

孤独患者 2024-09-18 00:01:11

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

可能读取导致了 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;
    } ...

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

The accepted answer worked for me until deployed on JDK 1.8u91.
Then I realized it failed at field.set(null, newValue); line when I had read the value via reflection before calling of setFinalStatic method.

Probably the read caused somehow different setup of Java reflection internals (namely sun.reflect.UnsafeQualifiedStaticObjectFieldAccessorImpl in failing case instead of sun.reflect.UnsafeStaticObjectFieldAccessorImpl in success case) but I didn't elaborate it further.

Since I needed to temporarily set new value based on old value and later set old value back, I changed signature little bit to provide computation function externally and also return old value:

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

However for general case this would not be sufficient.

半世蒼涼 2024-09-18 00:01:11

要使其适用于 JDK 21,您可以使用选项 -Djdk.reflect.useDirectMethodHandle=false

使 JDK 11 - 17 可访问

public static void setFieldAccessible(Field field) throws Exception {
    field.setAccessible(true);
    Method getDeclaredFields0 = Class.class.getDeclaredMethod("getDeclaredFields0", boolean.class);
    getDeclaredFields0.setAccessible(true);
    Field[] fields = (Field[]) getDeclaredFields0.invoke(Field.class, false);
    for (Field each : fields) {
        if ("modifiers".equals(each.getName())) {
            each.setAccessible(true);
            each.setInt(field, field.getModifiers() & ~Modifier.FINAL);
            break;
        }
    }
}

To make this worked with JDK 21 you can use option -Djdk.reflect.useDirectMethodHandle=false

Make accessable for JDK 11 - 17

public static void setFieldAccessible(Field field) throws Exception {
    field.setAccessible(true);
    Method getDeclaredFields0 = Class.class.getDeclaredMethod("getDeclaredFields0", boolean.class);
    getDeclaredFields0.setAccessible(true);
    Field[] fields = (Field[]) getDeclaredFields0.invoke(Field.class, false);
    for (Field each : fields) {
        if ("modifiers".equals(each.getName())) {
            each.setAccessible(true);
            each.setInt(field, field.getModifiers() & ~Modifier.FINAL);
            break;
        }
    }
}
无远思近则忧 2024-09-18 00:01:11

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

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

并抛出/处理 NoSuchFieldException

If your field is simply private you can do this:

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

and throw/handle NoSuchFieldException

知你几分 2024-09-18 00:01:11

评分最高的答案不适用于 Java 21 中使用 MethodHandles 和忽略 Field 抽象对象上的标志值。

一种解决方案是使用 Unsafe,但是使用 this JEP Unsafe 和重要的 long objectFieldOffset(Field f) 和
long staticFieldOffset(Field f) 方法已被弃用并被删除,因此,这在将来将不起作用:

final Unsafe unsafe = //..get Unsafe (...and add subsequent --add-opens statements for this to work)
final Field ourField = Example.class.getDeclaredField("changeThis");
final Object staticFieldBase = unsafe.staticFieldBase(ourField);
final long staticFieldOffset = unsafe.staticFieldOffset(ourField);
unsafe.putObject(staticFieldBase, staticFieldOffset, "it works");

我不推荐这样做,但在 Java 21 中,在进行繁重的工作时,可以使用新的反射实现如果确实需要,可以使用内部 API。

没有 Unsafe 的 Java 21+ 解决方案

其要点是使用 MethodHandle,它可以通过从内部 getDirectFieldNoSecurityManager(.. .) 的 Lookup 方法,通过向其提供一个 MemberName 来通过反射进行操作以从中删除最终标志。

您需要添加以下 JVM 选项才能使其正常工作:

--add-opens=java.base/java.lang.invoke=ALL-UNNAMED --add-opens=java.base/java.lang.reflect=ALL-UNNAMED
public class FinalFieldUtil {

    private static class MemberNameWrapper {

        protected  final Class<?> MEMBER_NAME_CLASS;
        private  final Constructor<?> MEMBER_NAME_CONSTRUCTOR;
        private  final Field MEMBER_NAME_FLAGS_FIELD;
        private  final Method MEMBER_NAME_GET_REFERENCE_KIND_METHOD;

         {
            try {
                MEMBER_NAME_CLASS = Class.forName("java.lang.invoke.MemberName");

                MEMBER_NAME_CONSTRUCTOR = MEMBER_NAME_CLASS.getDeclaredConstructor(Field.class, boolean.class); //e.g. new MemberName(myField, true);
                MEMBER_NAME_CONSTRUCTOR.setAccessible(true);

                MEMBER_NAME_FLAGS_FIELD = MEMBER_NAME_CLASS.getDeclaredField("flags");
                MEMBER_NAME_FLAGS_FIELD.setAccessible(true);

                MEMBER_NAME_GET_REFERENCE_KIND_METHOD = MEMBER_NAME_CLASS.getDeclaredMethod("getReferenceKind");
                MEMBER_NAME_GET_REFERENCE_KIND_METHOD.setAccessible(true);
            } catch (ClassNotFoundException | NoSuchMethodException | NoSuchFieldException e) {
                throw new RuntimeException(e);
            }
        }

        final Object instance;

        public MemberNameWrapper(Field field, boolean makeSetter) {
            try {
                instance = MEMBER_NAME_CONSTRUCTOR.newInstance(field, makeSetter);
                removeFinality(instance);

            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }

        private final void removeFinality(Object memberNameInstance) throws IllegalAccessException {
            //Manipulate flags to remove hints to it being final
            final int initialFlags = MEMBER_NAME_FLAGS_FIELD.getInt(memberNameInstance);

            if (!Modifier.isFinal(initialFlags)) {
                return;
            }

            final int nonFinalFlags = initialFlags & ~Modifier.FINAL;

            MEMBER_NAME_FLAGS_FIELD.setInt(memberNameInstance, nonFinalFlags);
        }

        protected Object getMemberNameInstance(){
            return instance;
        }

        protected byte getReferenceKind() {
            try {
                return (byte) MEMBER_NAME_GET_REFERENCE_KIND_METHOD.invoke(instance);
            } catch (IllegalAccessException | InvocationTargetException e) {
                throw new RuntimeException(e);
            }
        }

    }

    private static class LookupWrapper {

        private  final Class<?> LOOKUP_CLASS;
        private  final Method LOOKUP_GET_FIELD_VAR_HANDLE_NO_SECURITY_MANAGER_METHOD;

         {
            try {
                LOOKUP_CLASS = Lookup.class;
                LOOKUP_GET_FIELD_VAR_HANDLE_NO_SECURITY_MANAGER_METHOD = LOOKUP_CLASS.getDeclaredMethod("getDirectFieldNoSecurityManager", byte.class, Class.class, Class.forName("java.lang.invoke.MemberName"));
                LOOKUP_GET_FIELD_VAR_HANDLE_NO_SECURITY_MANAGER_METHOD.setAccessible(true);

            } catch (NoSuchMethodException | ClassNotFoundException e) {
                throw new RuntimeException(e);
            }

         }

        private final Lookup lookup;

        private LookupWrapper(Lookup lookup) {
            this.lookup = lookup;
        }

        public MethodHandle unreflectVarHandleUnrestricted(Field field) {
            final MemberNameWrapper memberNameSetterWrapper = new MemberNameWrapper(field, true);
            final byte setterReferenceKind = memberNameSetterWrapper.getReferenceKind();
            final Object memberNameSetter = memberNameSetterWrapper.getMemberNameInstance();


            try {
                return (MethodHandle) LOOKUP_GET_FIELD_VAR_HANDLE_NO_SECURITY_MANAGER_METHOD.invoke(lookup, setterReferenceKind, field.getDeclaringClass(), memberNameSetter);
            } catch (IllegalAccessException | InvocationTargetException e) {
                throw new RuntimeException(e);
            }
        }
    }

    public static void setStaticFinalField(Field field, Object value) throws Throwable {
        if (Modifier.isFinal(field.getModifiers()) && field.getType().isPrimitive()) {
            throw new IllegalArgumentException("primitive finals are not supported, because their modification depends on very specific circumstances.");
        }

        final LookupWrapper lookupWrapper = new LookupWrapper(MethodHandles.privateLookupIn(field.getDeclaringClass(), MethodHandles.lookup()));
        final MethodHandle methodHandle = lookupWrapper.unreflectVarHandleUnrestricted(field);
        methodHandle.invoke(value);

    }

}

然后您可以

public class TestModelClassWithFinalFields {

    public static final String myStr; //can be set
    public static final List<Object> myList = List.of(); //can be set
    public static final String myInlineStr = "init"; //cannot be set with this method!
    
    static {
        myStr = "initial static value";
    }
}
FinalFieldUtil.setStaticFinalField(TestModelClassWithFinalFields.class.getDeclaredField("myStr"), "new value!");

System.out.println(FinalFieldUtil.myStr);

请注意,当设置内联时,这不适用于纯字符串文字和原始值(例如 static final String = "str")

请参阅我的回答此处,了解有关使用内部 API 在 Java 21 中设置 Final 字段的初步尝试和想法没有不安全。

The top rated answer does not work with the new Reflection implementation of JEP416 in e.g. Java 21 that uses MethodHandles and ignores the flags value on the Field abstraction object.

One solution is to use Unsafe, however with this JEP Unsafe and the important long objectFieldOffset(Field f) and
long staticFieldOffset(Field f) methods are getting deprecated for removal so for example this will not work in the future:

final Unsafe unsafe = //..get Unsafe (...and add subsequent --add-opens statements for this to work)
final Field ourField = Example.class.getDeclaredField("changeThis");
final Object staticFieldBase = unsafe.staticFieldBase(ourField);
final long staticFieldOffset = unsafe.staticFieldOffset(ourField);
unsafe.putObject(staticFieldBase, staticFieldOffset, "it works");

I do not recommend this but it is possible in Java 21 with the new reflection implementation when making heavy use of the internal API if really needed.

Java 21+ solution without Unsafe

The gist of it is to use a MethodHandle that can write to a static final field by getting it from the internal getDirectFieldNoSecurityManager(...) method of the Lookup by providing it with a MemberName that is manipulated via Reflection to remove the final flag from it.

You will need to add the following JVM options for this to work:

--add-opens=java.base/java.lang.invoke=ALL-UNNAMED --add-opens=java.base/java.lang.reflect=ALL-UNNAMED
public class FinalFieldUtil {

    private static class MemberNameWrapper {

        protected  final Class<?> MEMBER_NAME_CLASS;
        private  final Constructor<?> MEMBER_NAME_CONSTRUCTOR;
        private  final Field MEMBER_NAME_FLAGS_FIELD;
        private  final Method MEMBER_NAME_GET_REFERENCE_KIND_METHOD;

         {
            try {
                MEMBER_NAME_CLASS = Class.forName("java.lang.invoke.MemberName");

                MEMBER_NAME_CONSTRUCTOR = MEMBER_NAME_CLASS.getDeclaredConstructor(Field.class, boolean.class); //e.g. new MemberName(myField, true);
                MEMBER_NAME_CONSTRUCTOR.setAccessible(true);

                MEMBER_NAME_FLAGS_FIELD = MEMBER_NAME_CLASS.getDeclaredField("flags");
                MEMBER_NAME_FLAGS_FIELD.setAccessible(true);

                MEMBER_NAME_GET_REFERENCE_KIND_METHOD = MEMBER_NAME_CLASS.getDeclaredMethod("getReferenceKind");
                MEMBER_NAME_GET_REFERENCE_KIND_METHOD.setAccessible(true);
            } catch (ClassNotFoundException | NoSuchMethodException | NoSuchFieldException e) {
                throw new RuntimeException(e);
            }
        }

        final Object instance;

        public MemberNameWrapper(Field field, boolean makeSetter) {
            try {
                instance = MEMBER_NAME_CONSTRUCTOR.newInstance(field, makeSetter);
                removeFinality(instance);

            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }

        private final void removeFinality(Object memberNameInstance) throws IllegalAccessException {
            //Manipulate flags to remove hints to it being final
            final int initialFlags = MEMBER_NAME_FLAGS_FIELD.getInt(memberNameInstance);

            if (!Modifier.isFinal(initialFlags)) {
                return;
            }

            final int nonFinalFlags = initialFlags & ~Modifier.FINAL;

            MEMBER_NAME_FLAGS_FIELD.setInt(memberNameInstance, nonFinalFlags);
        }

        protected Object getMemberNameInstance(){
            return instance;
        }

        protected byte getReferenceKind() {
            try {
                return (byte) MEMBER_NAME_GET_REFERENCE_KIND_METHOD.invoke(instance);
            } catch (IllegalAccessException | InvocationTargetException e) {
                throw new RuntimeException(e);
            }
        }

    }

    private static class LookupWrapper {

        private  final Class<?> LOOKUP_CLASS;
        private  final Method LOOKUP_GET_FIELD_VAR_HANDLE_NO_SECURITY_MANAGER_METHOD;

         {
            try {
                LOOKUP_CLASS = Lookup.class;
                LOOKUP_GET_FIELD_VAR_HANDLE_NO_SECURITY_MANAGER_METHOD = LOOKUP_CLASS.getDeclaredMethod("getDirectFieldNoSecurityManager", byte.class, Class.class, Class.forName("java.lang.invoke.MemberName"));
                LOOKUP_GET_FIELD_VAR_HANDLE_NO_SECURITY_MANAGER_METHOD.setAccessible(true);

            } catch (NoSuchMethodException | ClassNotFoundException e) {
                throw new RuntimeException(e);
            }

         }

        private final Lookup lookup;

        private LookupWrapper(Lookup lookup) {
            this.lookup = lookup;
        }

        public MethodHandle unreflectVarHandleUnrestricted(Field field) {
            final MemberNameWrapper memberNameSetterWrapper = new MemberNameWrapper(field, true);
            final byte setterReferenceKind = memberNameSetterWrapper.getReferenceKind();
            final Object memberNameSetter = memberNameSetterWrapper.getMemberNameInstance();


            try {
                return (MethodHandle) LOOKUP_GET_FIELD_VAR_HANDLE_NO_SECURITY_MANAGER_METHOD.invoke(lookup, setterReferenceKind, field.getDeclaringClass(), memberNameSetter);
            } catch (IllegalAccessException | InvocationTargetException e) {
                throw new RuntimeException(e);
            }
        }
    }

    public static void setStaticFinalField(Field field, Object value) throws Throwable {
        if (Modifier.isFinal(field.getModifiers()) && field.getType().isPrimitive()) {
            throw new IllegalArgumentException("primitive finals are not supported, because their modification depends on very specific circumstances.");
        }

        final LookupWrapper lookupWrapper = new LookupWrapper(MethodHandles.privateLookupIn(field.getDeclaringClass(), MethodHandles.lookup()));
        final MethodHandle methodHandle = lookupWrapper.unreflectVarHandleUnrestricted(field);
        methodHandle.invoke(value);

    }

}

And then you can

public class TestModelClassWithFinalFields {

    public static final String myStr; //can be set
    public static final List<Object> myList = List.of(); //can be set
    public static final String myInlineStr = "init"; //cannot be set with this method!
    
    static {
        myStr = "initial static value";
    }
}
FinalFieldUtil.setStaticFinalField(TestModelClassWithFinalFields.class.getDeclaredField("myStr"), "new value!");

System.out.println(FinalFieldUtil.myStr);

Please note that this will not work for pure String literals and primitive values when set inline (e.g. static final String = "str")

See my answer here for a the initial attempt and thoughts regarding using the internal API to set a final field in Java 21 without Unsafe.

年少掌心 2024-09-18 00:01:11

在 openjdk17 中,我不仅要从 Field.modifiers 中删除 final 修饰符,还要从 Field.root.modifiers 中删除。
Field.rootfield.set(...) 内部使用 此处
所以我的代码是这样的:

    private static void makeStaticFinalFieldWritable( Field field ) {
      try {
        var lookup = MethodHandles.privateLookupIn( field.getClass(), MethodHandles.lookup() );
        var modifiersHandle = lookup.findVarHandle( field.getClass(), "modifiers", int.class );
        var modifiers = field.getModifiers();
        modifiersHandle.set( field, modifiers & ~Modifier.FINAL );

        var rootHandle = lookup.findVarHandle( field.getClass(), "root", Field.class );
        var root = ( Field ) rootHandle.get( field );
        if ( root != null && root != field ) {
          makeStaticFinalFieldWritable( root );
        }
      }
      catch ( IllegalAccessException | NoSuchFieldException e ) {
        throw new Error( e );
      }
    }

In openjdk17 I had to remove final modifier not just from Field.modifiers, but also from Field.root.modifiers.
Field.root is used internally in field.set(...) here.
So my code is this:

    private static void makeStaticFinalFieldWritable( Field field ) {
      try {
        var lookup = MethodHandles.privateLookupIn( field.getClass(), MethodHandles.lookup() );
        var modifiersHandle = lookup.findVarHandle( field.getClass(), "modifiers", int.class );
        var modifiers = field.getModifiers();
        modifiersHandle.set( field, modifiers & ~Modifier.FINAL );

        var rootHandle = lookup.findVarHandle( field.getClass(), "root", Field.class );
        var root = ( Field ) rootHandle.get( field );
        if ( root != null && root != field ) {
          makeStaticFinalFieldWritable( root );
        }
      }
      catch ( IllegalAccessException | NoSuchFieldException e ) {
        throw new Error( e );
      }
    }
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文