javareflectionfinal

Changing private final fields via reflection


class WithPrivateFinalField {
    private final String s = "I’m totally safe";
    public String toString() {
        return "s = " + s;
    }
}
WithPrivateFinalField pf = new WithPrivateFinalField();
System.out.println(pf);
Field f = pf.getClass().getDeclaredField("s");
f.setAccessible(true);
System.out.println("f.get(pf): " + f.get(pf));
f.set(pf, "No, you’re not!");
System.out.println(pf);
System.out.println(f.get(pf));

Output:

s = I’m totally safe
f.get(pf): I’m totally safe
s = I’m totally safe
No, you’re not!

Why does it work by this way, can you please explain? The first print tells us that the private "s" field has not been changed, as I expect. But if we get the field via reflection, the second print shows, it is updated.


Solution

  • This answer is more than exhaustive on the topic.

    JLS 17.5.3 Subsequent Modification of Final Fields

    Even then, there are a number of complications. If a final field is initialized to a compile-time constant in the field declaration, changes to the final field may not be observed, since uses of that final field are replaced at compile time with the compile-time constant.

    But, if you read the paragraph above very carefully, you may find a way around here (set the private final field in the constructor instead of in the field definition):

    import java.lang.reflect.Field;
    
    
    public class Test {
    
      public static void main(String[] args) throws Exception {
        WithPrivateFinalField pf = new WithPrivateFinalField();
        System.out.println(pf);
        Field f = pf.getClass().getDeclaredField("s");
        f.setAccessible(true);
        System.out.println("f.get(pf): " + f.get(pf));
        f.set(pf, "No, you’re not!");
        System.out.println(pf);
        System.out.println("f.get(pf): " + f.get(pf));
      }
    
      private static class WithPrivateFinalField {
        private final String s;
    
        public WithPrivateFinalField() {
          this.s = "I’m totally safe";
        }
        public String toString() {
          return "s = " + s;
        }
      }
    
    }
    

    The output is then as follows:

    s = I’m totally safe
    f.get(pf): I’m totally safe
    s = No, you’re not!
    f.get(pf): No, you’re not!
    

    Hope this helps a bit.