Несмотря на то, что вы скрываете переменную, довольно интересно узнать, что вы можете изменить конечные поля в java, как вы можете прочитать здесь:
Java 5 — "final" больше не является окончательным
Narve Saetre из Machina Networks в Норвегии вчера прислал мне записку, упомянув, что очень жаль, что мы можем изменить дескриптор на окончательный массив. Я неправильно понял его и начал терпеливо объяснять, что мы не можем сделать массив постоянным и что нет никакого способа защитить содержимое массива. «Нет, — сказал он, — мы можем изменить финальную метку, используя отражение».
Я попробовал пример кода Narve, и, невероятно, Java 5 позволил мне изменить окончательный дескриптор, даже дескриптор примитивного поля! Я знал, что в какой-то момент это было разрешено, но затем было запрещено, поэтому я провел несколько тестов со старыми версиями Java. Во-первых, нам нужен класс с финальными полями:
public class Person {
private final String name;
private final int age;
private final int iq = 110;
private final Object country = "South Africa";
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String toString() {
return name + ", " + age + " of IQ=" + iq + " from " + country;
}
}
JDK 1.1.x
В JDK 1.1.x мы не могли получить доступ к закрытым полям с помощью отражения. Однако мы могли бы создать другого человека с публичными полями, затем скомпилировать наш класс для него и поменять местами классы Person. Не было проверки доступа во время выполнения, если мы работали с классом, отличным от того, с которым мы скомпилировали. Однако мы не могли перепривязать окончательные поля во время выполнения, используя обмен классами или отражение.
В JDK 1.1.8 JavaDocs для java.lang.reflect.Field было сказано следующее:
- Если этот объект Field обеспечивает управление доступом к языку Java, а базовое поле недоступно, метод выдает исключение IllegalAccessException.
- Если базовое поле является окончательным, метод создает исключение IllegalAccessException.
JDK 1.2.x
В JDK 1.2.x это немного изменилось. Теперь мы можем сделать приватные поля доступными с помощью метода setAccessible(true). Доступ к полям теперь проверялся во время выполнения, поэтому мы не могли использовать трюк с заменой классов для доступа к закрытым полям. Однако теперь мы можем внезапно переназначить конечные поля! Посмотрите на этот код:
import java.lang.reflect.Field;
public class FinalFieldChange {
private static void change(Person p, String name, Object value)
throws NoSuchFieldException, IllegalAccessException {
Field firstNameField = Person.class.getDeclaredField(name);
firstNameField.setAccessible(true);
firstNameField.set(p, value);
}
public static void main(String[] args) throws Exception {
Person heinz = new Person("Heinz Kabutz", 32);
change(heinz, "name", "Ng Keng Yap");
change(heinz, "age", new Integer(27));
change(heinz, "iq", new Integer(150));
change(heinz, "country", "Malaysia");
System.out.println(heinz);
}
}
Когда я запустил это в JDK 1.2.2_014, я получил следующий результат:
Ng Keng Yap, 27 of IQ=110 from Malaysia Note, no exceptions, no complaints, and an incorrect IQ result. It seems that if we set a
Последнее поле примитива во время объявления, значение встроено, если тип примитивный или String.
JDK 1.3.x и 1.4.x
В JDK 1.3.x компания Sun немного усложнила доступ и запретила нам изменять финальное поле с отражением. То же самое было и с JDK 1.4.x. Если бы мы попытались запустить класс FinalFieldChange для повторной привязки окончательных полей во время выполнения с помощью отражения, мы бы получили:
версия java "1.3.1_12": поток исключений "main" IllegalAccessException: поле является окончательным в java.lang.reflect.Field.set(собственный метод) в FinalFieldChange.change(FinalFieldChange.java:8) в FinalFieldChange.main(FinalFieldChange. ява:12)
версия java "1.4.2_05" Поток исключений "main" IllegalAccessException: поле является окончательным в java.lang.reflect.Field.set(Field.java:519) в FinalFieldChange.change(FinalFieldChange.java:8) в FinalFieldChange.main( FinalFieldChange.java:12)
JDK 5.x
Теперь мы подошли к JDK 5.x. Класс FinalFieldChange имеет тот же вывод, что и в JDK 1.2.x:
Ng Keng Yap, 27 of IQ=110 from Malaysia When Narve Saetre mailed me that he managed to change a final field in JDK 5 using
Поразмыслив, я надеялся, что в JDK закралась ошибка. Однако мы оба чувствовали, что это маловероятно, особенно такая фундаментальная ошибка. После некоторого поиска я нашел JSR-133: модель памяти Java и спецификацию потоков. Большая часть спецификации трудна для чтения и напоминает мне о моих университетских днях (раньше я так писал ;-). Однако JSR-133 настолько важен, что его следует прочитать всем программистам Java. (Удачи)
Начните с главы 9 Семантика конечного поля на стр. 25. В частности, прочитайте раздел 9.1.1 Модификация конечных полей после построения. Имеет смысл разрешить обновления конечных полей. Например, мы могли бы ослабить требование иметь поля, не являющиеся окончательными в JDO.
Если мы внимательно прочитаем раздел 9.1.1, мы увидим, что мы должны изменять только конечные поля как часть нашего процесса построения. Вариант использования заключается в том, что мы десериализуем объект, а затем, когда мы создали объект, мы инициализируем окончательные поля перед его передачей. После того, как мы сделали объект доступным для другого потока, мы не должны изменять конечные поля с помощью отражения. Результат не был бы предсказуем.
В нем даже говорится следующее: если конечное поле инициализируется константой времени компиляции в объявлении поля, изменения в конечном поле могут не наблюдаться, поскольку использование этого конечного поля заменяется во время компиляции константой времени компиляции. Это объясняет, почему наше поле iq остается прежним, а страна меняется.
Как ни странно, JDK 5 немного отличается от JDK 1.2.x тем, что вы не можете изменить статическое конечное поле.
import java.lang.reflect.Field;
public class FinalStaticFieldChange {
/** Static fields of type String or primitive would get inlined */
private static final String stringValue = "original value";
private static final Object objValue = stringValue;
private static void changeStaticField(String name)
throws NoSuchFieldException, IllegalAccessException {
Field statFinField = FinalStaticFieldChange.class.getDeclaredField(name);
statFinField.setAccessible(true);
statFinField.set(null, "new Value");
}
public static void main(String[] args) throws Exception {
changeStaticField("stringValue");
changeStaticField("objValue");
System.out.println("stringValue = " + stringValue);
System.out.println("objValue = " + objValue);
System.out.println();
}
}
Когда мы запускаем это с JDK 1.2.x и JDK 5.x, мы получаем следующий вывод:
версия Java "1.2.2_014": stringValue = исходное значение objValue = новое значение
версия java "1.5.0" Поток исключений "main" IllegalAccessException: поле является окончательным в java.lang.reflect.Field.set(Field.java:656) в FinalStaticFieldChange.changeStaticField(12) в FinalStaticFieldChange.main(16)
Итак, JDK 5 похож на JDK 1.2.x, только отличается?
Заключение
Вы знаете, когда был выпущен JDK 1.3.0? Я изо всех сил пытался выяснить, поэтому я скачал и установил его. Файл readme.txt имеет дату 2000/06/02 13:10. Итак, ему больше 4 лет (боже мой, как будто вчера). JDK 1.3.0 был выпущен за несколько месяцев до того, как я начал писать Информационный бюллетень специалистов по Java(tm)! Я думаю, можно с уверенностью сказать, что очень немногие Java-разработчики могут вспомнить детали до JDK1.3.0. Эх, ностальгия уже не та! Вы помните, как впервые запустили Java и получили эту ошибку: «Невозможно инициализировать потоки: невозможно найти класс java/lang/Thread»?