Java 为什么反射无法更新静态字段?

Java 为什么反射无法更新静态字段?,java,reflection,final,Java,Reflection,Final,有人能解释一下为什么下面的代码失败吗?我有以下五门课: public class TestReplaceLogger { public static void main(String[] arv) throws Exception { ClassWithFinalFields classWithFinalFields = new ClassWithFinalFields(); Field field = ClassWithFinalFields.class

有人能解释一下为什么下面的代码失败吗?我有以下五门课:

public class TestReplaceLogger {
    public static void main(String[] arv) throws Exception {
        ClassWithFinalFields classWithFinalFields = new ClassWithFinalFields();
        Field field = ClassWithFinalFields.class.getDeclaredField("LOG");

        // Comment this line and uncomment out the next line causes program work
        Logger oldLogger = (Logger)field.get(null);
        //Logger oldLogger = classWithFinalFields.LOG;


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

        field.set(null, new MockLogger());

        classWithFinalFields.log();
    }
}

public class ClassWithFinalFields {
    public static final Logger LOG = new RealLogger();

    public void log() {
        LOG.log("hello");
    }
}

public interface Logger {
    public void log(String msg);
}

public class RealLogger implements Logger{
    public void log(String msg) {
        System.out.println("RealLogger: " + msg);
    }
}

public class MockLogger implements Logger {
    public void log(String msg) {
        System.out.println("MockLogger: " + msg);
    }
}
代码试图做的是使用反射来替换ClassWithFinalFields类中的LOG变量。目前,当类试图在
TestReplaceLogger
的末尾设置字段时,会抛出一个
IllegalAccessException

但是,如果我替换

Logger oldLogger = (Logger)field.get(null);

然后,代码运行正常,并按预期打印日志“MockLogger:hello”

所以问题是,为什么通过反射读取最后一个字段会阻止程序工作?看起来最后一个修饰符不能再被删除了,所以你得到了一个IllegalAccessException,但我不知道为什么。我可以推测这可能与编译器优化或类加载器排序有关,但是,尽管看了字节码,我还是不知道到底发生了什么

如果人们想知道我为什么要这么做,那么一开始我们是在寻找一种方法,在我们升级一些软件时,在单元测试期间模拟一些笨拙的日志记录。现在我只是好奇,到底是什么在掩盖下

如果有人想看到它,堆栈跟踪是

Exception in thread "main" java.lang.IllegalAccessException: Can not set static final org.matthelliwell.reflection.Logger field org.matthelliwell.reflection.ClassWithFinalFields.LOG to org.matthelliwell.reflection.MockLogger
at sun.reflect.UnsafeFieldAccessorImpl.throwFinalFieldIllegalAccessException(UnsafeFieldAccessorImpl.java:73)
at sun.reflect.UnsafeFieldAccessorImpl.throwFinalFieldIllegalAccessException(UnsafeFieldAccessorImpl.java:77)
at sun.reflect.UnsafeQualifiedStaticObjectFieldAccessorImpl.set(UnsafeQualifiedStaticObjectFieldAccessorImpl.java:77)
at java.lang.reflect.Field.set(Field.java:741)
at org.matthelliwell.reflection.TestReplaceLogger.main(TestReplaceLogger.java:23)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:120)

您正在绕过字段对象的公共api访问该字段对象。当然,如果你那样做,任何事情都可能发生。特别是,JavaAPI的不同实现可能表现出不同的行为


在OracleJDK中,字段假定修饰符是最终的,因此缓存fieldAccessor(请参见
Field.getFieldAccessor()
)。您已经更改了修饰符,但没有使该缓存无效,从而使用了旧的字段访问器,它仍然认为该字段是最终字段。

只需移动这些行:

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

Logger oldLogger = (Logger)field.get(null);

在对字段执行任何操作之前,必须修改字段的修饰符,否则在第一次操作时,JVM将缓存字段元数据(如@meriton和@oleg.Lukyrch所述)。

也有类似的问题,这几乎让我毛骨悚然
这里的代码(简化)可以完美地工作

        Field field = Long.class.getDeclaredField("MIN_VALUE");
        field.get(null);

        Field modifiersField = Field.class.getDeclaredField("modifiers");
        modifiersField.setAccessible(true);
        modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
        field.get(null);
…这个会抛出一个“非法访问异常”


(出于上述原因)

您可以发布堆栈跟踪吗?我已将堆栈跟踪添加到问题中。谢谢,缓存可以解释问题。(为我辩护,我知道任何事情都有可能发生,我只是好奇在幕后发生了什么)
        Field field = Long.class.getDeclaredField("MIN_VALUE");
        field.get(null);

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

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