Изменение финальных полей в Java - PullRequest
51 голосов
/ 23 октября 2009

Давайте начнем с простого теста:

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());
  }
}

Кто-нибудь захочет угадать, что будет напечатано как вывод (показан внизу, чтобы не испортить сюрприз сразу).

Вопросы:

  1. Почему примитивное и упакованное целое число ведут себя по-разному?
  2. Почему рефлексивный и прямой доступ дают разные результаты?
  3. Тот, что меня больше всего мучает - почему String ведет себя как примитив int и не как Integer?

Результаты (Java 1.5):

reflection: primitiveInt = 84
direct: primitiveInt = 42
reflection: wrappedInt = 84
direct: wrappedInt = 84
reflection: stringValue = 84
direct: stringValue = 42

Ответы [ 5 ]

21 голосов
/ 23 октября 2009

Константы времени компиляции встроены (во время компиляции javac). См. JLS, в частности 15.28 определяет константное выражение, а 13.4.9 обсуждает двоичную совместимость или конечные поля и константы.

Если вы сделаете поле не финальным или назначите постоянную времени без компиляции, значение не будет встроено. Например:

private final String stringValue = null! = Null? "": "42";

9 голосов
/ 08 февраля 2011

На мой взгляд, это еще хуже: коллега указал на следующую забавную вещь:

@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)

7 голосов
/ 21 января 2010

Метод отражения set(..) работает с FieldAccessor с.

Для int он получает UnsafeQualifiedIntegerFieldAccessorImpl, суперкласс которого определяет свойство readOnly как истинное, только если поле имеет значение и 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 должно происходить только сразу после инициализации объекта.

1 голос
/ 24 октября 2009

Это не ответ, но он вызывает еще одну путаницу:

Я хотел посмотреть, связана ли проблема с оценкой во время компиляции или же рефлексия фактически позволяет Java обойти ключевое слово final. Вот тестовая программа. Все, что я добавил, было еще одним набором вызовов геттеров, так что есть один до и после каждого 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 ()?

0 голосов
/ 31 декабря 2013

Для этого есть обходной путь. если вы установите значение частного статического final filed в блоке static {}, оно будет работать, потому что оно не будет встроено в 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");
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...