Изменить частное статическое конечное поле с помощью отражения 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"

Смежные вопросы


Предостережения

Следует проявлять крайнюю осторожность, когда вы делаете что-то подобное. Это может не сработать, потому что 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а также MethodHandles как часть 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 на первом месте.

Другие вопросы по тегам