Changing private final fields via reflection

前端 未结 4 1015
鱼传尺愫
鱼传尺愫 2020-11-28 07:20
class WithPrivateFinalField {
    private final String s = \"I’m totally safe\";
    public String toString() {
        return \"s = \" + s;
    }
}
WithPrivateFinal         


        
相关标签:
4条回答
  • 2020-11-28 07:45

    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 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.

    0 讨论(0)
  • 2020-11-28 07:52

    This

    class WithPrivateFinalField {
        private final String s = "I’m totally safe";
        public String toString() {
            return "s = " + s;
        }  
    } 
    

    actually compiles like this:

    class WithPrivateFinalField {
        private final String s = "I’m totally safe";
        public String toString() {
            return "s = I’m totally safe";
        }  
    }
    

    That is, compile-time constants get inlined. See this question. The easiest way to avoid inlining is to declare the String like this:

    private final String s = "I’m totally safe".intern();
    

    For other types, a trivial method call does the trick:

    private final int integerConstant = identity(42);
    private static int identity(int number) {
        return number;
    }
    
    0 讨论(0)
  • 2020-11-28 07:57

    Being final, the compiler expected the value not to change, so it probably hardcoded the string directly into your toString method.

    0 讨论(0)
  • 2020-11-28 08:00

    Here's a decompile of WithPrivateFinalField class file (I put it in a separate class for simplicity):

      WithPrivateFinalField();
         0  aload_0 [this]
         1  invokespecial java.lang.Object() [13]
         4  aload_0 [this]
         5  ldc <String "I’m totally safe"> [8]
         7  putfield WithPrivateFinalField.s : java.lang.String [15]
        10  return
          Line numbers:
            [pc: 0, line: 2]
            [pc: 4, line: 3]
            [pc: 10, line: 2]
          Local variable table:
            [pc: 0, pc: 11] local: this index: 0 type: WithPrivateFinalField
    
      // Method descriptor #22 ()Ljava/lang/String;
      // Stack: 1, Locals: 1
      public java.lang.String toString();
        0  ldc <String "s = I’m totally safe"> [23]
        2  areturn
          Line numbers:
            [pc: 0, line: 6]
          Local variable table:
            [pc: 0, pc: 3] local: this index: 0 type: WithPrivateFinalField
    

    Note in the toString() method, the constant used at address 0 [0 ldc <String "s = I’m totally safe"> [23]] shows the compiler already concatenated the string literal "s = " and the private final field " I’m totally safe" together in advance and stored it. The toString() method will always return "s = I’m totally safe" regardless of how the instance variable s changes.

    0 讨论(0)
提交回复
热议问题