Изменение частного статического конечного поля с использованием отражения 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);

Ответ 1

Предполагая, что 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
  • Reflection используется для изменения 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, он напомнил мне, что Constant Expressions обрабатываются разными, поэтому его невозможно будет изменить. Следовательно, вам нужно будет изменить свой код, чтобы он выглядел так:

public class A {
    private final String myVar;

    private A() {
        myVar = "Some Value";
    }
}

если вы не являетесь владельцем класса... Я чувствую вас!

Для получения дополнительной информации о том, почему это поведение читает это?

Ответ 2

Если значение, присвоенное полю static final boolean, известно во время компиляции, оно является константой. Поля примитива или Тип String может быть константой времени компиляции. Константа будет встроена в любой код, который ссылается на поле. Поскольку поле на самом деле не читается во время выполнения, его изменение не будет иметь никакого эффекта.

Спецификация языка Java гласит:

Если поле является постоянной переменной (§4.12.4), затем удаляя ключевое слово окончательный или изменение его значения не будет нарушить совместимость с уже существующими двоичные файлы, заставляя их не работать, но они не увидят никакого нового значения для использования поля, если они перекомпилированы. Это верно, даже если само использование не время компиляции константное выражение (§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

Ответ 3

Небольшое любопытство к языковой спецификации 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#jls-17.5.4

Ответ 4

Я также интегрировал его с библиотекой joor

Просто используйте

      Reflect.on(yourObject).set("finalFieldName", finalFieldValue);

Также я исправил проблему с override которое, по-видимому, пропустило предыдущие решения. Однако используйте это очень осторожно, только когда нет другого хорошего решения.

Ответ 5

В случае наличия диспетчера безопасности можно использовать 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;
});

Ответ 6

Наряду с ответом с наивысшим рейтингом вы можете использовать немного более простой подход. В классе Apache commons FieldUtils уже есть конкретный метод, который может это делать. Пожалуйста, взгляните на метод FieldUtils.removeFinalModifier. Вы должны указать экземпляр целевого поля и флаг форсирования доступности (если вы играете с закрытыми полями). Более подробную информацию вы можете найти здесь here.

Ответ 7

Принятый ответ работал на меня до развертывания на 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;
    } ...

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

Ответ 8

Если ваше поле просто личное, вы можете сделать это:

MyClass myClass= new MyClass();
Field aField= myClass.getClass().getDeclaredField("someField");
aField.setAccessible(true);
aField.set(myClass, "newValueForAString");

и бросить/обработать NoSuchFieldException

Ответ 9

Даже несмотря на то, что 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.

Ответ 10

Просто увидел этот вопрос по одному из вопросов интервью, если возможно изменить финальную переменную с отражением или во время выполнения. Мне очень интересно, так что я стал с:

 /**
 * @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();
    }
}

Простой класс с конечной переменной String. Итак, в основном классе 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

Ответ 11

Весь смысл поля final заключается в том, что его нельзя переназначить после установки. JVM использует эту гарантию для обеспечения согласованности в разных местах (например, внутренние классы, ссылающиеся на внешние переменные). Так нет. Возможность сделать это нарушит JVM!

Решение состоит не в том, чтобы объявить его final в первую очередь.