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

我有一个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);

假设没有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
  • 反射用于改变public static final Boolean.FALSE来引用由Boolean.TRUE引用的Boolean
  • 其结果是,随后每当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 ,因为它可以作为编译时常量进行内联,因此“新”值可能不可观察

  • 附录:关于按位操作

    从本质上讲,

    field.getModifiers() & ~Modifier.FINAL
    

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

    也可以看看

  • 维基百科/按位操作

  • 如果分配给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
    

    来自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

    链接地址: http://www.djcxy.com/p/21209.html

    上一篇: Change private static final field using Java reflection

    下一篇: Reflecting parameter name: abuse of C# lambda expressions or Syntax brilliance?