Изменение частных финальных полей через отражение
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" не было изменено, как я ожидаю. Но если мы получим поле с помощью отражения, второй отпечаток показывает, что оно обновляется.
4 ответа
Этот ответ более чем исчерпывающий по теме.
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!
Надеюсь это немного поможет.
Это
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";
}
}
То есть константы времени компиляции становятся встроенными. Смотрите этот вопрос. Самый простой способ избежать встраивания - объявить String
как это:
private final String s = "I’m totally safe".intern();
Для других типов тривиальный вызов метода делает свое дело:
private final int integerConstant = identity(42);
private static int identity(int number) {
return number;
}
Вот декомпиляция WithPrivateFinalField
файл класса (для простоты я поместил его в отдельный класс):
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
изменения.
Бытие final
, компилятор ожидал, что значение не изменится, поэтому он, вероятно, жестко закодировал строку прямо в ваш toString
метод.