Изменение частных финальных полей через отражение - PullRequest
50 голосов
/ 23 декабря 2010
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 ]

70 голосов
/ 23 декабря 2010

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

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!

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

15 голосов
/ 23 декабря 2010

Это

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;
}
6 голосов
/ 23 декабря 2010

Вот декомпиляция файла класса 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.

1 голос
/ 23 декабря 2010

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...