Изменение приватного статического финального поля с помощью рефлексии в Java
У меня есть класс с полем <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 ответ(ов)
В соответствии с Java Language Specification, глава 17, раздел 17.5.4 "Поле с защитой от записи":
В общем случае, поле, которое является final и static, не может быть изменено. Однако, поля System.in, System.out и System.err являются статическими финальными полями, которые, по причинам совместимости, должны иметь возможность изменяться с помощью методов System.setIn, System.setOut и System.setErr. Мы называем эти поля защищенными от записи, чтобы отличать их от обычных финальных полей.
Источник: Java Language Specification
В случае присутствия 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, и безопасно установить доступность полей класса.
Многие из представленных здесь ответов полезны, но я не нашел ни одного, который работал бы именно на 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)
).
Да, вы правильно заметили, что с помощью рефлексии в Java можно изменить значение final
переменной, хотя это и не является рекомендуемой практикой. Пример, который вы привели, демонстрирует, как можно это сделать.
Вот пошаговый разбор вашего кода:
- Вы создали класс
SomeClass
с финальной строковой переменнойstr
, значение которой устанавливается в конструкторе. - В методе
main
, вы создаете экземплярSomeClass
и выводите его состояние на экран. - Далее вы используете рефлексию, чтобы получить доступ к полю
str
, пометом устанавливаете для него доступ с помощьюfield.setAccessible(true)
. - Затем вы изменяете значение этого поля на "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
было успешно изменено. Спасибо за интересный подход и пример!
Понял вашу проблему. Принятый ответ работал для меня, пока я не развернул проект на 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;
} ...
Однако для общего случая этого может быть недостаточно.
Как получить значение закрытого поля из другого класса в Java?
Изменение приватных финальных полей с помощью рефлексии
Выполнение кода, содержащегося в строке
Java: статическое поле в абстрактном классе
Можно ли использовать статический метод в абстрактном классе?