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

Alexandr
  • 9,213
  • 12
  • 62
  • 102
  • See this: http://stackoverflow.com/questions/3301635/change-private-static-final-field-using-java-reflection/31268945#31268945 – iirekm Jul 07 '15 at 12:49
  • http://stackoverflow.com/questions/3301635/change-private-static-final-field-using-java-reflection/31268945#31268945 – iirekm Jul 07 '15 at 12:50

4 Answers4

72

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.

Community
  • 1
  • 1
Jiri Patera
  • 3,140
  • 1
  • 20
  • 14
  • 1
    +1 - this is the real answer. The OP's program is causing the JIT compiler's assumption that `s` doesn't change to be invalid. This is not a compiler bug, since the JLS explicitly warns that "bad things" like this can happen if you modify `final` variables after they have been set. – Stephen C Dec 23 '10 at 07:18
  • 5
    @Stephen C: Actually the behavior has nothing to do with *JIT* compiling. It's already the bytecode compiler that replaces the `s` in `return "s = " + s;` with the compile-time constant. This can be demonstrated by making two classes, `A` and `B` so that `A` refers to a constant defined in `B`. Now, changing the constant in `B` and recompiling only `B` will keep the old constant in `A`! Sneaky, but true. – Joonas Pulakka Dec 23 '10 at 09:10
  • @Joonas - ok ... but it would also be legitimate for the JIT compiler to do this kind of optimization. – Stephen C Dec 23 '10 at 13:12
  • 1
    @Stephen C: Indeed. IMO it would be way *better* if the JIT compiler would do that instead of the bytecode compiler. With the current behavior, you can break someone else's class just by modifying the *value* of a, say, flag constant in your class. – Joonas Pulakka Dec 23 '10 at 13:29
  • Should I call `f.setAccessible(true);` after that to restore field modifiers? – Lutosław Feb 28 '17 at 10:15
  • 2
    I think you don't have to restore the field accessibility as it is in fact a copy of some Java internal `Field` instance. Such a copy is just for you to use. So if you don't need to read the original `accessible` flag state later on in your code, you don't have to keep and then restore its state. – Jiri Patera Mar 20 '17 at 12:55
  • not working with java 17 – Tarik Mar 07 '23 at 19:43
18

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;
}
Community
  • 1
  • 1
Joonas Pulakka
  • 36,252
  • 29
  • 106
  • 169
7

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.

Bert F
  • 85,407
  • 12
  • 106
  • 123
1

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

Gabe
  • 84,912
  • 12
  • 139
  • 238