Изменение приватных финальных полей с помощью рефлексии
Проблема с доступом к приватным финальным полям в Java
Я столкнулся с неожиданным поведением в Java, связанным с доступом к приватным финальным полям с использованием рефлексии. Вот небольшой пример кода, который демонстрирует эту проблему:
class WithPrivateFinalField {
private final String s = "I’m totally safe";
public String toString() {
return "s = " + s;
}
}
WithPrivateFinalField pf = new WithPrivateFinalField();
System.out.println(pf); // Вывод: s = I’m totally safe
Field f = pf.getClass().getDeclaredField("s");
f.setAccessible(true);
System.out.println("f.get(pf): " + f.get(pf)); // Вывод: f.get(pf): I’m totally safe
f.set(pf, "No, you’re not!"); // Изменение значения через рефлексию
System.out.println(pf); // Вывод: s = I’m totally safe
System.out.println(f.get(pf)); // Вывод: No, you’re not!
Ожидаемое поведение
Первый вызов System.out.println(pf);
возвращает строку "s = I’m totally safe", что логично, так как поле s
является приватным и финальным, и я ожидаю, что его значение не может быть изменено после инициализации.
Непонятное поведение
Однако после изменения значения поля s
через рефлексию, вызов System.out.println(pf);
снова возвращает "s = I’m totally safe", в то время как f.get(pf);
возвращает "No, you’re not!".
Вопрос
Почему так происходит? Как работает механизм финальных полей в Java в контексте рефлексии? Почему вывод при использовании System.out.println(pf);
не отражает изменения, сделанные через рефлексию, в то время как f.get(pf);
показывает обновленное значение?
2 ответ(ов)
В вашем вопросе вы привели декомпиляцию класса WithPrivateFinalField
. Давайте проанализируем сгенерированный код и поймём, как это работает.
В конструкторе 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
Как видно, в начале создается новый объект, затем вызывается конструктор родительского класса Object
. Затем происходит загрузка текущего объекта (this
) и инициализация поля s
строкой "I’m totally safe"
. В итоге, поле s
инициализируется только один раз при создании объекта.
Теперь давайте посмотрим на метод toString()
:
public java.lang.String toString();
0 ldc <String "s = I’m totally safe"> [23]
2 areturn
Метод просто возвращает строку, в которой уже содержится значение поля s
. Заметьте, что строка "s = I’m totally safe"
конкатенируется компилятором и хранится как константа на этапе компиляции, что означает, что независимо от изменений поля s
после создания объекта, toString()
всегда будет возвращать одно и то же значение.
Таким образом, даже если вы измените значение поля s
после создания объекта, результат вызова toString()
не изменится. Это связано с тем, что строка возвращается в виде константы, а не берётся из поля объекта.
Поскольку переменная объявлена как final
, компилятор ожидает, что её значение не изменится, поэтому, вероятно, строка была закодирована непосредственно в ваш метод toString
.
Изменение приватного статического финального поля с помощью рефлексии в Java
Как создать обобщённый массив в Java?
Получить обобщённый тип класса во время выполнения
Выполнение кода, содержащегося в строке
Как вызвать метод суперкласса с использованием рефлексии в Java