0

Изменение приватных финальных полей с помощью рефлексии

22

Проблема с доступом к приватным финальным полям в 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 ответ(ов)

0

В вашем вопросе вы привели декомпиляцию класса 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() не изменится. Это связано с тем, что строка возвращается в виде константы, а не берётся из поля объекта.

0

Поскольку переменная объявлена как final, компилятор ожидает, что её значение не изменится, поэтому, вероятно, строка была закодирована непосредственно в ваш метод toString.

Чтобы ответить на вопрос, пожалуйста, войдите или зарегистрируйтесь