为什么我不能在更改字段的修饰符之前使用方法 get(java.lang.reflect.Field#get)

why I can`t use method get(java.lang.reflect.Field#get) before changing field`s modifiers

java代码如下

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;

public class Test {
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
        C c = new C();
        Field field = c.getClass().getDeclaredField("NAME");
        field.setAccessible(true);
        System.out.println(field.get(c));//Cause program exception on line 15 while using method get(java.lang.reflect.Field#get).

        Field modifiers = field.getClass().getDeclaredField("modifiers");
        modifiers.setAccessible(true);
        modifiers.setInt(field, field.getModifiers() & ~Modifier.FINAL);
        System.out.println(Modifier.toString(field.getModifiers()));
        field.set(c,"James");
        System.out.println(field.get(c));
    }

}

class C{
    private static final String NAME = "Clive";

    public String toString(){
        return NAME;
    }
}

使用java.lang.reflect.Field#set时出现异常。异常信息如下。但是,如果我删除第9行的代码(System.out.println(field.get(c));),没有发生异常

Exception in thread "main" java.lang.IllegalAccessException: Can not set static final java.lang.String field C.NAME to java.lang.String
    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 Test.main(Test.java:15)

您遇到此问题是因为以下行

System.out.println(field.get(c));

由于默认情况下 Field 假定修饰符为 final,JDK 将在您调用对它的任何操作时缓存该字段。现在,在代码的后面部分,您将通过以下行修改字段的访问权限

 modifiers.setInt(field, field.getModifiers() & ~Modifier.FINAL);

但是由于您没有使缓存失效,您会收到以下异常。

因此,如果您注释掉 get 语句,您的访问修饰符逻辑将正常工作而不会抛出任何异常

简而言之,您需要在调用与字段关联的任何操作之前调用修饰符逻辑。第一次调用它时,元数据将被缓存

Field 懒惰地创建一个名为 FieldAccessor 的对象,它实际上负责 getset 操作。这可以在 Field.get (archive). You can click on the method getFieldAccessor to go deeper in to the call stack. This will (at the moment) eventually take you to a method sun.reflect.UnsafeFieldAccessorFactory.newFieldAccessor (archive) 的源代码中看到,您可以在其中看到修饰符被读取一次,然后被烘焙到字段访问器的实际类型中。

在更改修饰符之前调用 Field.get 会影响输出,因为它会导致在删除 final 之前实例化字段访问器。

您可以使用类似下面的代码来清除字段访问器:

public static void clearFieldAccessors(Field field)
        throws ReflectiveOperationException {
    Field fa = Field.class.getDeclaredField("fieldAccessor");
    fa.setAccessible(true);
    fa.set(field, null);

    Field ofa = Field.class.getDeclaredField("overrideFieldAccessor");
    ofa.setAccessible(true);
    ofa.set(field, null);

    Field rf = Field.class.getDeclaredField("root");
    rf.setAccessible(true);
    Field root = (Field) rf.get(field);
    if (root != null) {
        clearFieldAccessors(root);
    }
}

如果您在 field.get(...)field.set(...) 之间插入 clearFieldAccessors(field),则使用它会导致问题中的代码通过。

当然,不能保证任何这些都一定有效,而且 clearFieldAccessors 中的代码可能会导致一些我不知道的问题。