Подтвердить что ты не робот

Изменение частных конечных полей посредством отражения

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

Вывод:

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

Почему это работает таким образом, не могли бы вы объяснить? Первая печать сообщает нам, что частное поле "s" не было изменено, как я ожидаю. Но если мы получим поле через отражение, то вторая печать показывает, что он обновлен.

4b9b3361

Ответ 1

Этот ответ более чем исчерпывающий по этой теме.

JLS 17.5.3 Последующая модификация конечных полей

Даже тогда есть ряд осложнений. Если конечное поле инициализируется константой времени компиляции в объявлении поля, изменения в конечном поле могут не наблюдаться, поскольку использование этого окончательное поле заменяется во время компиляции с временем компиляции константа.

Но, если вы внимательно прочитаете абзац выше, вы можете найти способ здесь (установите в поле private final в конструкторе вместо определения поля):

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

}

Выход следующий:

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

Надеюсь, это немного поможет.

Ответ 2

Это

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

действительно компилируется следующим образом:

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

То есть, константы времени компиляции становятся inlined. См. этот вопрос. Самый простой способ избежать наложения - объявить String следующим образом:

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

Для других типов тривиальный вызов метода делает трюк:

private final int integerConstant = identity(42);
private static int identity(int number) {
    return number;
}

Ответ 3

Вот декомпилировать файл класса WithPrivateFinalField 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

Примечание в методе toString() константа, используемая по адресу 0 [0 ldc <String "s = I’m totally safe"> [23]], показывает, что компилятор уже конкатенировал строковый литерал "s = " и закрытое конечное поле " I’m totally safe" вместе заранее и сохранил его. Метод toString() всегда будет возвращать "s = I’m totally safe" независимо от того, как изменяется переменная экземпляра s.

Ответ 4

Будучи final, компилятор ожидал, что значение не изменится, поэтому он, вероятно, жестко закодировал строку непосредственно в ваш метод toString.