首页 文章

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

提问于
浏览
398

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

使用反射我收到此错误: java.lang.IllegalAccessException: Can not set static final boolean field

有没有办法改变 Value ?

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

9 回答

  • 6

    假设没有 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 "constants" Boolean.TRUEBoolean.FALSE

    • 反射用于更改public static final Boolean.FALSE以引用 Boolean.TRUE 引用的 Boolean

    • 结果,随后每当 false 被自动装箱到 Boolean.FALSE 时,它指的是与 Boolean.TRUE 所指的 Boolean 相同的 Boolean .

    • 现在 "false" 的所有内容都是 "true"

    相关问题


    警告

    每当你做这样的事情时,都应该格外小心 . 它可能不起作用,因为 SecurityManager 可能存在,但即使它不存在,取决于使用模式,它可能或可能不起作用 .

    JLS 17.5.3最终字段的后续修改在某些情况下,例如反序列化,系统将需要在构造后更改对象的最终字段 . 最终字段可以通过反射和其他依赖于实现的方式来改变 . 其中具有合理语义的唯一模式是构造对象然后更新对象的最终字段的模式 . 对象不应该对其他线程可见,也不应该读取最终字段,直到完成对象的最终字段的所有更新 . 最终字段的冻结既发生在设置了最终字段的构造函数的末尾,也发生在通过反射或其他特殊机制对每个最终字段进行修改之后 . 即使这样,也有许多并发症 . 如果在字段声明中将final字段初始化为编译时常量,则可能无法观察到对final字段的更改,因为在编译时将该final字段的使用替换为编译时常量 . 另一个问题是规范允许对最终字段进行积极优化 . 在一个线程中,允许使用构造函数中不发生的最终字段的那些修改来重新排序最终字段的读取 .

    另见

    • JLS 15.28 Constant Expression

    • 这种技术不太可能与原始 private static final boolean 一起使用,因为它可以作为编译时常量进行内联,因此"new"值可能无法被观察到


    附录:关于按位操作

    实质上,

    field.getModifiers() & ~Modifier.FINAL
    

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

    另见


    记住常量表达式

    仍然无法解决这个问题?,就像我为此做的那样陷入了萧条?你的代码看起来像这样吗?

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

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

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

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

    有关此行为的原因的详细信息read this

  • 12

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

    Java language specification这样说:

    如果字段是常量变量(第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
    
  • 4

    从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

  • 4

    我也把它与joor library整合在一起

    只是用

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

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

  • 48

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

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

  • 2

    接受的答案对我有用,直到部署在JDK 1.8u91上 . 然后我意识到它在 field.set(null, newValue); 行失败了,当我在调用 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;
        } ...
    

    但是对于一般情况,这是不够的 .

  • -5

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

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

  • 756

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

    解决方案不是首先声明它 final .

相关问题