Changing Private Finite Fields Through 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 this way, could you please explain? The first seal tells us that the private field "s" has not been changed as I expect. But if we get the field through reflection, then the second print shows that it is updated.

+48
java reflection final
Dec 23 '10 at 6:25
source share
4 answers

This answer is more than exhaustive on this topic.

JLS 17.5.3 Subsequent Modification of Finite Fields

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

But, if you carefully read the paragraph above, you can find a way here (set the private final field in the constructor instead of 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 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.

+67
Dec 23 '10 at 7:14
source share

it

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

really compiles as follows:

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

That is, compile-time constants become inlined. See this question. The easiest way to avoid overlapping is to declare a String as follows:

 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; } 
+15
Dec 23 '10 at 7:22
source share

Here's the decompile class file WithPrivateFinalField class (I just put it in a separate class):

  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] ] indicates that the compiler has already concatenated the string literal "s = " and the closed final field " I'm totally safe" together in advance and saved it. The toString () method will always return "s = I'm totally safe" no matter how the instance variable s changes.

+5
Dec 23 '10 at 7:35
source share

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

+1
Dec 23 '10 at 7:15
source share



All Articles