5

Изменение приватного статического финального поля с помощью рефлексии в Java

26

У меня есть класс с полем <code>private static final</code>, которое, к сожалению, мне нужно изменить во время выполнения программы.

При использовании рефлексии я получаю следующую ошибку: <code>java.lang.IllegalAccessException: Can not set static final boolean field</code>.

Существует ли способ изменить значение этого поля?

Вот пример кода, который я использую:

Field hack = WarpTransform2D.class.getDeclaredField("USE_HACK");
hack.setAccessible(true);
hack.set(null, true);

5 ответ(ов)

0

В соответствии с Java Language Specification, глава 17, раздел 17.5.4 "Поле с защитой от записи":

В общем случае, поле, которое является final и static, не может быть изменено. Однако, поля System.in, System.out и System.err являются статическими финальными полями, которые, по причинам совместимости, должны иметь возможность изменяться с помощью методов System.setIn, System.setOut и System.setErr. Мы называем эти поля защищенными от записи, чтобы отличать их от обычных финальных полей.

Источник: Java Language Specification

0

В случае присутствия Security Manager можно использовать AccessController.doPrivileged.

Возьмем тот же пример из принятого ответа выше:

import java.lang.reflect.*;

public class EverythingIsTrue {
    static void setFinalStatic(Field field, Object newValue) throws Exception {
        field.setAccessible(true);
        Field modifiersField = Field.class.getDeclaredField("modifiers");

        // Оборачивание setAccessible 
        AccessController.doPrivileged(new PrivilegedAction() {
            @Override
            public Object run() {
                modifiersField.setAccessible(true);
                return null;
            }
        });

        modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
        field.set(null, newValue);
    }

    public static void main(String args[]) throws Exception {      
        setFinalStatic(Boolean.class.getField("FALSE"), true);
        System.out.format("Everything is %s", false); // "Everything is true"
    }
}

В выражении лямбда-функции AccessController.doPrivileged можно упростить до следующего:

AccessController.doPrivileged((PrivilegedAction) () -> {
    modifiersField.setAccessible(true);
    return null;
});

Таким образом, использование doPrivileged позволяет обойти ограничения, накладываемые Security Manager, и безопасно установить доступность полей класса.

0

Многие из представленных здесь ответов полезны, но я не нашел ни одного, который работал бы именно на Android. Я достаточно активно использую Reflect от joor, и ни он, ни FieldUtils от apache, упоминаемые в некоторых ответах, не сработали.

Проблема с Android

Основная причина заключается в том, что в классе Field на Android отсутствует поле modifiers, что делает любые предложения, основанные на этом коде (как в отмеченном ответе), бесполезными:

Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);

На самом деле, чтобы процитировать FieldUtils.removeFinalModifier():

// Реализует ли каждый JRE Field с приватной переменной "modifiers"?
final Field modifiersField = Field.class.getDeclaredField("modifiers");

Так что ответ - нет...

Решение

Все довольно просто: вместо modifiers, имя поля - accessFlags. Это сработает:

Field accessFlagsField = Field.class.getDeclaredField("accessFlags");
accessFlagsField.setAccessible(true);
accessFlagsField.setInt(field, field.getModifiers() & ~Modifier.FINAL);

Замечание #1: это будет работать независимо от того, является ли поле статическим в классе или нет.

Замечание #2: Поскольку само поле может быть приватным, рекомендуется также включить доступ к самому полю, используя field.setAccessible(true) (в дополнение к accessFlagsField.setAccessible(true)).

0

Да, вы правильно заметили, что с помощью рефлексии в Java можно изменить значение final переменной, хотя это и не является рекомендуемой практикой. Пример, который вы привели, демонстрирует, как можно это сделать.

Вот пошаговый разбор вашего кода:

  1. Вы создали класс SomeClass с финальной строковой переменной str, значение которой устанавливается в конструкторе.
  2. В методе main, вы создаете экземпляр SomeClass и выводите его состояние на экран.
  3. Далее вы используете рефлексию, чтобы получить доступ к полю str, пометом устанавливаете для него доступ с помощью field.setAccessible(true).
  4. Затем вы изменяете значение этого поля на "There you are" и снова выводите состояние экземпляра SomeClass.

Ваш результат подтверждает, что значение переменной str изменилось, хотя оно и помечено как final.

Однако, стоит отметить, что такой подход может привести к неожиданному поведению и нарушению принципов инкапсуляции. Изменение final полей с помощью рефлексии может сделать код трудным для понимания и сопровождения. Рекомендуется избегать таких манипуляций, если это возможно.

Ваш код и его вывод:

Class name: class SomeClass Value: This is the string that never changes!
Class name: class SomeClass Value: There you are

Эти строки подтверждают, что значение переменной str было успешно изменено. Спасибо за интересный подход и пример!

0

Понял вашу проблему. Принятый ответ работал для меня, пока я не развернул проект на JDK 1.8u91. Я обнаружил, что он не работает на строке field.set(null, newValue);, когда я предварительно считал значение через рефлексию перед вызовом метода setFinalStatic.

Вероятно, чтение значения каким-то образом вызвало другую настройку внутренних механизмов рефлексии в Java, а именно sun.reflect.UnsafeQualifiedStaticObjectFieldAccessorImpl в случае неудачи, вместо sun.reflect.UnsafeStaticObjectFieldAccessorImpl в случае успеха, но я не стал подробно исследовать этот момент.

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

public static <T> T assignFinalField(Object object, Class<?> clazz, String fieldName, UnaryOperator<T> newValueFunction) {
    Field f = null, ff = null;
    try {
        f = clazz.getDeclaredField(fieldName);
        final int oldM = f.getModifiers();
        final int newM = oldM & ~Modifier.FINAL;
        ff = Field.class.getDeclaredField("modifiers");
        ff.setAccessible(true);
        ff.setInt(f,newM);
        f.setAccessible(true);

        T result = (T)f.get(object);
        T newValue = newValueFunction.apply(result);

        f.set(object,newValue);
        ff.setInt(f,oldM);

        return result;
    } ...

Однако для общего случая этого может быть недостаточно.

Чтобы ответить на вопрос, пожалуйста, войдите или зарегистрируйтесь