Search code examples
javareflectionfieldfinal

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


java code as follow.

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

An exception occured when I use java.lang.reflect.Field#set。Exception information as follow. However,if I delete the code(System.out.println(field.get(c));) on line 9,no exception occured

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)

Solution

  • Field lazily creates an object called a FieldAccessor which is actually responsible for get and set operations. This can be seen in the source code for 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) where you can see that the modifiers are read once and then baked in to the actual type of the field accessor.

    Calling Field.get before changing the modifiers affects the output because it causes the field accessor to be instantiated before final is removed.

    You could possibly use something like the following bit of code to clear the field accessors:

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

    Using that causes the code in the question to pass, if you insert clearFieldAccessors(field) in between field.get(...) and field.set(...).

    There is, of course, no guarantee that any of this has to work, and it's possible that the code in clearFieldAccessors will cause some problem that I'm unaware of.