Изменение конечных полей в Java
давайте начнем с простого тестового случая:
import java.lang.reflect.Field;
public class Test {
private final int primitiveInt = 42;
private final Integer wrappedInt = 42;
private final String stringValue = "42";
public int getPrimitiveInt() { return this.primitiveInt; }
public int getWrappedInt() { return this.wrappedInt; }
public String getStringValue() { return this.stringValue; }
public void changeField(String name, Object value) throws IllegalAccessException, NoSuchFieldException {
Field field = Test.class.getDeclaredField(name);
field.setAccessible(true);
field.set(this, value);
System.out.println("reflection: " + name + " = " + field.get(this));
}
public static void main(String[] args) throws IllegalAccessException, NoSuchFieldException {
Test test = new Test();
test.changeField("primitiveInt", 84);
System.out.println("direct: primitiveInt = " + test.getPrimitiveInt());
test.changeField("wrappedInt", 84);
System.out.println("direct: wrappedInt = " + test.getWrappedInt());
test.changeField("stringValue", "84");
System.out.println("direct: stringValue = " + test.getStringValue());
}
}
кто-нибудь хочет угадать, что будет напечатано в качестве вывода (показана внизу, чтобы сразу не испортить сюрприз).
вопросы:
- почему примитивное и обернутое целое число ведут себя по-разному?
- почему reflective vs direct access возвращает разные результаты?
- тот, который меня больше всего беспокоит - почему строка ведет себя как примитивная
intи не какInteger?
результаты (java 1.5):
reflection: primitiveInt = 84
direct: primitiveInt = 42
reflection: wrappedInt = 84
direct: wrappedInt = 84
reflection: stringValue = 84
direct: stringValue = 42
5 ответов:
константы времени компиляции встроены (во время компиляции javac). См. JLS, в частности 15.28 определяет постоянное выражение и 13.4.9 обсуждает двоичную совместимость или конечные поля и константы.
Если вы делаете поле не является окончательной или назначить не время компиляции константы, значения не встроен. Например:
private final String stringValue = null!=null?"": "42";
на мой взгляд, это еще хуже: коллега указал на следующую забавную вещь:
@Test public void testInteger() throws SecurityException, NoSuchFieldException, IllegalArgumentException, IllegalAccessException { Field value = Integer.class.getDeclaredField("value"); value.setAccessible(true); Integer manipulatedInt = Integer.valueOf(7); value.setInt(manipulatedInt, 666); Integer testInt = Integer.valueOf(7); System.out.println(testInt.toString()); }делая это, вы можете изменить поведение всего JVM, в котором вы работаете. (конечно, вы можете изменить только значения в диапазоне от -127 до 127)
отражение
set(..)метод работает сFieldAccessors.на
intполучаетUnsafeQualifiedIntegerFieldAccessorImpl, чей суперкласс определяетreadOnlyсвойство true только в том случае, если поле иstaticиfinalтак, чтобы сначала ответить на незаданный вопрос - вот почему
finalизменен без исключения.все подклассы
UnsafeQualifiedFieldAccessorиспользоватьsun.misc.Unsafeкласс для получения значений. Методы есть всеnative, но их имена являютсяgetVolatileInt(..)иgetInt(..)(getVolatileObject(..)иgetObject(..)соответственно). Вышеупомянутые методы доступа используют "изменчивую" версию. Вот что произойдет, если мы добавим энергонезависимую версию:System.out.println("reflection: non-volatile primitiveInt = " unsafe.getInt(test, (long) unsafe.fieldOffset(getField("primitiveInt"))));(где
unsafeсоздается путем отражения-в противном случае это не допускается) (и я зовуgetObjectнаIntegerиString)что дает некоторые интересные результаты:
reflection: primitiveInt = 84 direct: primitiveInt = 42 reflection: non-volatile primitiveInt = 84 reflection: wrappedInt = 84 direct: wrappedInt = 84 reflection: non-volatile wrappedInt = 84 reflection: stringValue = 84 direct: stringValue = 42 reflection: non-volatile stringValue = 84в этот момент я вспоминаю статья на javaspecialists.eu обсуждаем связанный с этим вопрос. Он цитирует JSR-133:
если конечное поле инициализируется константой времени компиляции в объявлении поля, изменения в конечном поле могут не наблюдаться, так как использование этого конечного поля заменяется во время компиляции константой времени компиляции.
в главе 9 обсуждаются детали, наблюдаемые в этом вопросе.
и оказывается такое поведение не так уж и неожиданно, так как модификации
finalполя должны происходить только сразу после инициализации объекта.
это не ответ, но еще один момент смятения:
Я хотел посмотреть, была ли проблема во время компиляции или отражение действительно позволяло Java обойти
finalключевое слово. Вот тестовая программа. Все, что я добавил, Это еще один набор вызовов getter, поэтому есть один до и после каждогоchangeField()звонок.package com.example.gotchas; import java.lang.reflect.Field; public class MostlyFinal { private final int primitiveInt = 42; private final Integer wrappedInt = 42; private final String stringValue = "42"; public int getPrimitiveInt() { return this.primitiveInt; } public int getWrappedInt() { return this.wrappedInt; } public String getStringValue() { return this.stringValue; } public void changeField(String name, Object value) throws IllegalAccessException, NoSuchFieldException { Field field = MostlyFinal.class.getDeclaredField(name); field.setAccessible(true); field.set(this, value); System.out.println("reflection: " + name + " = " + field.get(this)); } public static void main(String[] args) throws IllegalAccessException, NoSuchFieldException { MostlyFinal test = new MostlyFinal(); System.out.println("direct: primitiveInt = " + test.getPrimitiveInt()); test.changeField("primitiveInt", 84); System.out.println("direct: primitiveInt = " + test.getPrimitiveInt()); System.out.println(); System.out.println("direct: wrappedInt = " + test.getWrappedInt()); test.changeField("wrappedInt", 84); System.out.println("direct: wrappedInt = " + test.getWrappedInt()); System.out.println(); System.out.println("direct: stringValue = " + test.getStringValue()); test.changeField("stringValue", "84"); System.out.println("direct: stringValue = " + test.getStringValue()); } }вот результат, который я получаю (под Eclipse, Java 1.6)
direct: primitiveInt = 42 reflection: primitiveInt = 84 direct: primitiveInt = 42 direct: wrappedInt = 42 reflection: wrappedInt = 84 direct: wrappedInt = 84 direct: stringValue = 42 reflection: stringValue = 84 direct: stringValue = 42Почему, черт возьми, прямой вызов чтобы getWrappedInt () изменить ?
есть работа вокруг этого. если вы зададите значение частного статического конечного файла в статическом блоке {}, он будет работать, потому что он не будет встроен в поле fileld:
private static final String MY_FIELD; static { MY_FIELD = "SomeText" } ... Field field = VisitorId.class.getDeclaredField("MY_FIELD"); field.setAccessible(true); field.set(field, "fakeText");
Comments