Изменить частное статическое конечное поле с помощью отражения Java
У меня есть класс с private static final
поле, которое, к сожалению, мне нужно изменить во время выполнения.
Используя отражение, я получаю эту ошибку: java.lang.IllegalAccessException: Can not set static final boolean field
Есть ли способ изменить значение?
Field hack = WarpTransform2D.class.getDeclaredField("USE_HACK");
hack.setAccessible(true);
hack.set(null, true);
15 ответов
При условии нет SecurityManager
мешает вам сделать это, вы можете использовать setAccessible
обойти private
и сбросить модификатор, чтобы избавиться от final
и на самом деле изменить private static final
поле.
Вот пример:
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");
modifiersField.setAccessible(true);
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"
}
}
При условии нет SecurityException
брошен, вышеприведенный код печатает "Everything is true"
,
То, что на самом деле сделано здесь, выглядит следующим образом:
- Примитив
boolean
ценностиtrue
а такжеfalse
вmain
автоматически упакованы в ссылочный типBoolean
"константа"Boolean.TRUE
а такжеBoolean.FALSE
- Отражение используется для изменения
public static final Boolean.FALSE
ссылаться наBoolean
упоминаетсяBoolean.TRUE
- В результате впоследствии всякий раз, когда
false
автоматически упакован вBoolean.FALSE
, это относится к тому жеBoolean
как упомянутоBoolean.TRUE
- Все что было
"false"
сейчас"true"
Смежные вопросы
- Используя отражение, чтобы изменить
static final File.separatorChar
для модульного тестирования - Как ограничить setAccessible только "законным" использованием?
- Есть примеры возни с
Integer
Кеш, мутировавшийString
, так далее
- Есть примеры возни с
Предостережения
Следует проявлять крайнюю осторожность, когда вы делаете что-то подобное. Это может не сработать, потому что SecurityManager
может присутствовать, но даже если это не так, в зависимости от модели использования, он может или не может работать.
JLS 17.5.3 Последующая модификация конечных полей
В некоторых случаях, таких как десериализация, система должна будет изменить
final
поля объекта после строительства.final
поля могут быть изменены с помощью отражения и других зависящих от реализации средств. Единственный шаблон, в котором это имеет разумную семантику, это тот, в котором объект создается, а затемfinal
поля объекта обновляются. Объект не должен быть видимым для других потоков, а также не долженfinal
поля должны быть прочитаны, пока все обновленияfinal
поля объекта заполнены. Зависаетfinal
поле происходит как в конце конструктора, в которомfinal
поле установлено, и сразу после каждой модификацииfinal
поле через отражение или другой специальный механизм.Даже тогда, есть ряд осложнений. Если
final
поле инициализируется константой времени компиляции в объявлении поля, изменяется наfinal
поле может не наблюдаться, так как использование этогоfinal
поле заменяется во время компиляции на постоянную времени компиляции.Другая проблема заключается в том, что спецификация допускает агрессивную оптимизацию
final
поля. В пределах потока допустимо изменить порядок чтенияfinal
поле с теми модификациями конечного поля, которые не имеют места в конструкторе.
Смотрите также
- JLS 15.28 Константа Выражение
- Вряд ли эта техника работает с примитивом
private static final boolean
потому что он встроен как константа времени компиляции и, таким образом, "новое" значение может быть не наблюдаемым
- Вряд ли эта техника работает с примитивом
Приложение: О побитовой манипуляции
По существу,
field.getModifiers() & ~Modifier.FINAL
выключает бит, соответствующий Modifier.FINAL
от field.getModifiers()
, &
является побитовым и ~
является побитовым дополнением.
Смотрите также
Помните постоянные выражения
Все еще не в состоянии решить это?, Впал в депрессию, как я сделал для этого? Ваш код выглядит так?
public class A {
private final String myVar = "Some Value";
}
Прочитав комментарии к этому ответу, особенно @Pshemo, он напомнил мне, что выражения констант обрабатываются по-разному, поэтому изменить их будет невозможно. Следовательно, вам нужно будет изменить свой код, чтобы он выглядел так:
public class A {
private final String myVar;
private A() {
myVar = "Some Value";
}
}
если ты не владелец класса... я тебя чувствую!
Для более подробной информации о том, почему это поведение читать это?
Если значение присвоено static final boolean
поле известно во время компиляции, оно является константой. Поля примитива или String
type может быть константой времени компиляции. Константа будет встроена в любой код, который ссылается на поле. Поскольку поле на самом деле не читается во время выполнения, его изменение не будет иметь никакого эффекта.
Спецификация языка Java говорит это:
Если поле является константной переменной (§4.12.4), то удаление ключевого слова final или изменение его значения не нарушит совместимость с уже существующими двоичными файлами, заставив их не запускаться, но они не увидят никакого нового значения для использования. поля, если они не перекомпилированы. Это верно, даже если само использование не является константным выражением времени компиляции (§15.28)
Вот пример:
class Flag {
static final boolean FLAG = true;
}
class Checker {
public static void main(String... argv) {
System.out.println(Flag.FLAG);
}
}
Если вы декомпилируете Checker
вы увидите, что вместо ссылки Flag.FLAG
код просто выдвигает значение 1 (true
) в стек (инструкция № 3).
0: getstatic #2; //Field java/lang/System.out:Ljava/io/PrintStream;
3: iconst_1
4: invokevirtual #3; //Method java/io/PrintStream.println:(Z)V
7: return
Небольшое любопытство из Спецификации языка Java, глава 17, раздел 17.5.4 "Поля, защищенные от записи":
Обычно поле, которое является окончательным и статическим, не может быть изменено. Однако System.in, System.out и System.err являются статическими конечными полями, которые по старым причинам должны быть разрешены для изменения методами System.setIn, System.setOut и System.setErr. Мы называем эти поля защищенными от записи, чтобы отличать их от обычных конечных полей.
Источник: http://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html
Я также интегрировал его с библиотекой Joor
Просто используйте
Reflect.on(yourObject).set("finalFieldName", finalFieldValue);
Также я исправил проблему с override
которые предыдущие решения, кажется, упускают. Однако используйте это очень осторожно, только когда нет другого хорошего решения.
Наряду с ответом с наивысшим рейтингом вы можете использовать более простой подход. Apache Commons FieldUtils
класс уже имеет определенный метод, который может делать вещи. Пожалуйста, посмотрите на FieldUtils.removeFinalModifier
метод. Вы должны указать целевой экземпляр поля и флаг принудительного доступа (если вы играете с закрытыми полями). Более подробную информацию вы можете найти здесь.
Даже несмотря на то, что final
поле может быть изменено вне статического инициализатора и (по крайней мере, JVM HotSpot) отлично выполнит байт-код.
Проблема в том, что компилятор Java не позволяет этого, но это можно легко обойти, используя objectweb.asm
. Вот совершенно допустимый файл класса, который проходит проверку байт-кода и успешно загружен и инициализирован под JVM HotSpot OpenJDK12:
ClassWriter cw = new ClassWriter(0);
cw.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, "Cl", null, "java/lang/Object", null);
{
FieldVisitor fv = cw.visitField(Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC | Opcodes.ACC_FINAL, "fld", "I", null, null);
fv.visitEnd();
}
{
// public void setFinalField1() { //... }
MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, "setFinalField1", "()V", null, null);
mv.visitMaxs(2, 1);
mv.visitInsn(Opcodes.ICONST_5);
mv.visitFieldInsn(Opcodes.PUTSTATIC, "Cl", "fld", "I");
mv.visitInsn(Opcodes.RETURN);
mv.visitEnd();
}
{
// public void setFinalField2() { //... }
MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, "setFinalField2", "()V", null, null);
mv.visitMaxs(2, 1);
mv.visitInsn(Opcodes.ICONST_2);
mv.visitFieldInsn(Opcodes.PUTSTATIC, "Cl", "fld", "I");
mv.visitInsn(Opcodes.RETURN);
mv.visitEnd();
}
cw.visitEnd();
В Java класс выглядит примерно так:
public class Cl{
private static final int fld;
public static void setFinalField1(){
fld = 5;
}
public static void setFinalField2(){
fld = 2;
}
}
который не может быть скомпилирован с javac
, но может быть загружен и запущен JVM.
JVM HotSpot имеет особую обработку таких классов в том смысле, что предотвращает участие таких "констант" в сворачивании констант. Эта проверка выполняется на этапе перезаписи байт-кода при инициализации класса:
// Check if any final field of the class given as parameter is modified
// outside of initializer methods of the class. Fields that are modified
// are marked with a flag. For marked fields, the compilers do not perform
// constant folding (as the field can be changed after initialization).
//
// The check is performed after verification and only if verification has
// succeeded. Therefore, the class is guaranteed to be well-formed.
InstanceKlass* klass = method->method_holder();
u2 bc_index = Bytes::get_Java_u2(bcp + prefix_length + 1);
constantPoolHandle cp(method->constants());
Symbol* ref_class_name = cp->klass_name_at(cp->klass_ref_index_at(bc_index));
if (klass->name() == ref_class_name) {
Symbol* field_name = cp->name_ref_at(bc_index);
Symbol* field_sig = cp->signature_ref_at(bc_index);
fieldDescriptor fd;
if (klass->find_field(field_name, field_sig, &fd) != NULL) {
if (fd.access_flags().is_final()) {
if (fd.access_flags().is_static()) {
if (!method->is_static_initializer()) {
fd.set_has_initialized_final_update(true);
}
} else {
if (!method->is_object_initializer()) {
fd.set_has_initialized_final_update(true);
}
}
}
}
}
}
Единственное ограничение, которое проверяет JVM HotSpot, заключается в том, что final
поле не должно изменяться вне класса, в котором final
поле объявлено в.
В случае присутствия диспетчера безопасности можно использовать 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");
// wrapping 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;
});
Начиная с Java 12 и новее, приведенные ответы не будут работать.
Вот пример того, как изменить
private static final
поле с Java 12 (на основе ).
private Object modifyField(Object newFieldValue, String fieldName, Object classInstance) throws NoSuchFieldException, IllegalAccessException {
Field field = classInstance.getClass().getDeclaredField(fieldName);
VarHandle MODIFIERS;
field.setAccessible(true);
var lookup = MethodHandles.privateLookupIn(Field.class, MethodHandles.lookup());
MODIFIERS = lookup.findVarHandle(Field.class, "modifiers", int.class);
int mods = field.getModifiers();
if (Modifier.isFinal(mods)) {
MODIFIERS.set(field, mods & ~Modifier.FINAL);
}
Object previousValue = field.get(classInstance);
field.set(null, newFieldValue);
return previousValue;
}
Смотрите этого ответаэту ветку для более подробной информации.
С JDK 18 это будет невозможно из-за повторной реализации основного отражения над
invokedynamic
а также
MethodHandle
s как часть JEP-416 (PR).
Цитата Мэнди Чанг — главного автора этой невероятной работы — в следующем комментарии. Акценты мои.
Если базовое поле является окончательным, объект имеет доступ для записи тогда и только тогда, когда
setAccessible(true)
преуспел в этомField
объект;- поле не статично; а также
- класс объявления поля не является скрытым классом; а также
- класс объявления поля не является классом записи.
Многие ответы здесь полезны, но я не нашел ни одного из них, над которым можно работать.
Android
, в частности. Я даже изрядно пользуюсь
Reflect
по joor, и ни он, ни apache's
FieldUtils
- оба упомянутых здесь в некоторых ответах делают свое дело.
Проблема с Android
Основная причина этого в том, что на Android нет
modifiers
поле в
Field
class, который делает любые предложения, связанные с этим кодом (как в отмеченном ответе), бесполезными:
Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
Фактически, чтобы процитировать
FieldUtils.removeFinalModifier()
:
// Do all JREs implement Field with a private ivar called "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)
.
Принятый ответ работал для меня, пока не был развернут на 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;
} ...
Однако для общего случая этого было бы недостаточно.
Только что увидел этот вопрос на одном из вопросов интервью, если это возможно, чтобы изменить окончательную переменную с отражением или во время выполнения. Стало действительно интересно, так что я стал с чем:
/**
* @author Dmitrijs Lobanovskis
* @since 03/03/2016.
*/
public class SomeClass {
private final String str;
SomeClass(){
this.str = "This is the string that never changes!";
}
public String getStr() {
return str;
}
@Override
public String toString() {
return "Class name: " + getClass() + " Value: " + getStr();
}
}
Какой-то простой класс с конечной строковой переменной. Так что в главном классе import java.lang.reflect.Field;
/**
* @author Dmitrijs Lobanovskis
* @since 03/03/2016.
*/
public class Main {
public static void main(String[] args) throws Exception{
SomeClass someClass = new SomeClass();
System.out.println(someClass);
Field field = someClass.getClass().getDeclaredField("str");
field.setAccessible(true);
field.set(someClass, "There you are");
System.out.println(someClass);
}
}
Вывод будет следующим:
Class name: class SomeClass Value: This is the string that never changes!
Class name: class SomeClass Value: There you are
Process finished with exit code 0
Согласно документации https://docs.oracle.com/javase/tutorial/reflect/member/fieldValues.html
Чтобы это работало с JDK 21, вы можете использовать опцию-Djdk.reflect.useDirectMethodHandle=false
Сделать доступным для JDK 11–17
public static void setFieldAccessible(Field field) throws Exception {
field.setAccessible(true);
Method getDeclaredFields0 = Class.class.getDeclaredMethod("getDeclaredFields0", boolean.class);
getDeclaredFields0.setAccessible(true);
Field[] fields = (Field[]) getDeclaredFields0.invoke(Field.class, false);
for (Field each : fields) {
if ("modifiers".equals(each.getName())) {
each.setAccessible(true);
each.setInt(field, field.getModifiers() & ~Modifier.FINAL);
break;
}
}
}
Если ваше поле просто личное, вы можете сделать это:
MyClass myClass= new MyClass();
Field aField= myClass.getClass().getDeclaredField("someField");
aField.setAccessible(true);
aField.set(myClass, "newValueForAString");
и бросить / обработать NoSuchFieldException
Весь смысл final
поле в том, что он не может быть переназначен после установки. JVM использует эту гарантию для поддержания согласованности в различных местах (например, внутренние классы, ссылающиеся на внешние переменные). Так что нет. Возможность сделать это сломала бы JVM!
Решение не объявить это final
на первом месте.