изменение конечных переменных посредством отражения, почему разница между статической и нестатической конечной переменной

Пожалуйста, обратитесь к приведенному ниже коду. Когда я запускаю код, я могу изменить значение конечной нестатической переменной. Но если я попытаюсь изменить значение конечной статической переменной, она выдаст java.lang.IllegalAccessException.

Мой вопрос в том, почему он не генерирует исключение в случае нестатической конечной переменной или наоборот. Почему разница?

import java.lang.reflect.Field;
import java.util.Random;

public class FinalReflection {

    final static int stmark =  computeRandom();
    final int inmark = computeRandom();

    public static void main(String[] args) throws SecurityException, NoSuchFieldException, IllegalArgumentException, IllegalAccessException {
        FinalReflection obj = new FinalReflection();
        System.out.println(FinalReflection.stmark);
        System.out.println(obj.inmark);
        Field staticFinalField  = FinalReflection.class.getDeclaredField("stmark");
        Field instanceFinalField  = FinalReflection.class.getDeclaredField("inmark");
        staticFinalField.setAccessible(true);
        instanceFinalField.setAccessible(true);

        instanceFinalField.set(obj, 100);
        System.out.println(obj.inmark);

        staticFinalField.set(FinalReflection.class, 101);
        System.out.println(FinalReflection.stmark);

    }

    private static int computeRandom() {
        return new Random().nextInt(5);
    }
}

person veritas    schedule 08.08.2013    source источник
comment
Я разместил код, который не дает исключений. Но это точно взлом.   -  person Narendra Pathai    schedule 08.08.2013


Ответы (3)


FinalReflectionobj = new FinalReflection();
System.out.println(FinalReflection.stmark);
System.out.println(obj.inmark);
Field staticFinalField  = FinalReflection.class.getDeclaredField("stmark");
Field instanceFinalField  = FinalReflection.class.getDeclaredField("inmark");
staticFinalField.setAccessible(true);
instanceFinalField.setAccessible(true);

//EXTRA CODE
//Modify the final using reflection
Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(staticFinalField, staticFinalField.getModifiers() & ~Modifier.FINAL);


instanceFinalField.set(obj, 100);
System.out.println(obj.inmark);
staticFinalField.set(FinalReflection.class, 101);
System.out.println(FinalReflection.stmark);

Это решение не лишено некоторых недостатков, оно может работать не во всех случаях:

В случае, если поле final инициализировано константой времени компиляции в объявлении поля, изменения в поле final могут быть не видны, поскольку использование этого конечного поля заменяется во время компиляции константой времени компиляции.

Другая проблема заключается в том, что спецификация допускает агрессивную оптимизацию final полей. В потоке допустимо переупорядочивать чтение поля final с теми модификациями поля final, которые не выполняются в конструкторе. Подробнее об этом также объясняется в этом аналогичном вопросе.

person Narendra Pathai    schedule 08.08.2013
comment
@assylias это позволит вам также изменить статическое конечное поле, если нет менеджера безопасности. - person Narendra Pathai; 08.08.2013
comment
Обратите внимание, что это не будет работать со статическими конечными примитивами, инициализированными константным выражением. - person assylias; 08.08.2013
comment
@assylias да, это не всегда работает, но в этом случае сработает. Спасибо, что указали на это. Добавлю и кейсы. - person Narendra Pathai; 08.08.2013

javadoc ясен:

Если базовое поле является окончательным, метод выдает исключение IllegalAccessException, если setAccessible(true) для этого объекта поля не был выполнен успешно и поле не является статическим.

С точки зрения JLS точное поведение отражения не указано, но в JLS 17.5.4:

Обычно поле, которое является окончательным и статическим, не может быть изменено.

Одним из обходных путей является удаление модификатора final посредством отражения.

person assylias    schedule 08.08.2013
comment
Отличный ответ, но я думаю, что вопрос скорее в том, почему java-дизайнеры так решили? - person morgano; 08.08.2013
comment
@morgano, если это так, ТАК, вероятно, не лучшее место, чтобы спрашивать! Это, безусловно, вызывает всевозможные проблемы. Например, примитивные константы встраиваются во время компиляции, поэтому вы не можете изменить их во время выполнения, если не измените базовый байт-код. - person assylias; 08.08.2013
comment
@assylias спасибо за отличную информацию. но да, мой вопрос был мало связан с тем, что сказал Моргано. Но да, я не могу не согласиться с тем, что SO, возможно, не лучшее место, чтобы спросить об этом. Я буду помнить об этом в следующий раз. В любом случае встроенный примитив подходит как для статических, так и для нестатических конечных переменных. попробуйте запустить измененное кодовое значение 5 вместо calculateRandom(). - person veritas; 08.08.2013

Для final ему могут быть присвоены разные значения во время выполнения при инициализации.

Class Test{    
public final int a;
}

Test t1  = new Test();
t1.a = 10;
Test t2  = new Test();
t1.a = 20;

Таким образом, каждый экземпляр имеет разное значение поля a.

Для статического финала все экземпляры имеют одно и то же значение и не могут быть изменены после первой инициализации.

Class TestStatic{
   public static final int a;
}

Test t1  = new Test();
t1.a = 10;
Test t2  = new Test();
t1.a = 20;   // ERROR, CAN'T BE ALTERED AFTER THE FIRST INITIALIZATION.
person Johnny Broberg    schedule 08.08.2013
comment
Но он использует только один единственный экземпляр класса. И нестатическая конечная переменная не может быть изменена после первого присваивания. Поэтому это объяснение неверно. - person Uwe Plonus; 08.08.2013