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

479

У меня есть класс с 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);
fixitagain
источник
4
Такая плохая идея. Я бы попытался получить исходный код и перекомпилировать (или даже декомпилировать / перекомпилировать) вместо этого.
Билл К
System.out является публичным статическим конечным полем, но его также можно изменить.
неопровержимый
19
@irreputable System.out/in/errнастолько «особенный», что в модели памяти Java следует особо упоминать их. Это не примеры, которым нужно следовать.
Том Хотин - снасть
8
Что ж, моя точка зрения заключается в том, чтобы найти хак между тем, чтобы мое приложение работало, пока ответственный lib не внес изменения в следующем выпуске, так что мне больше не нужно взламывать ...
fixitagain
1
@Bill K десять лет назад: было бы здорово перекомпилировать его, но он находится в развернутой системе, и мне просто нужно исправить его, пока мы не сможем обновить развернутое приложение!
Билл К

Ответы:

888

Предполагая, что нет 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
  • В результате, впоследствии, когда a 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";
    }
}

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

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

polygenelubricants
источник
41
@thecoop, @HalfBrian: нет сомнений в том, что это ЧРЕЗВЫЧАЙНО ЗЛО , но этот пример был выбран специально. Мой ответ показывает только, как в некоторых случаях это возможно. Самый отвратительный пример, который я могу придумать, - это преднамеренный выбор с надеждой на то, что, возможно, люди будут испытывать отвращение, а не влюбляться в технику.
полигенасмазочные материалы
59
Эй, черт. Я слышал, что вам нравится отражение, поэтому я размышлял над полем, чтобы вы могли размышлять, пока размышляете.
Мэтью Флэшен
11
Обратите внимание, что Boolean.FALSE не является приватным. Действительно ли это работает с "private final static" членами?
mgaert
15
@mgaert это так, но вы должны использовать getDeclaredField()вместо getField()целевого класса
Eis
11
+1. Для тех, кто попытается изменить что-то подобное final String myConstant = "x";и потерпит неудачу: помните, что константы времени компиляции будут встроены компилятором, поэтому, когда вы будете писать код так, как System.out.println(myConstant);он будет скомпилирован, System.out.println("x");потому что компилятор знает значение константы во время компиляции. Чтобы избавиться от этой проблемы, вам нужно инициализировать ваши константы во время выполнения, как final String myConstant = new String("x");. Кроме того, в случае примитивов нравится final int myField = 11использовать final int myField = new Integer(11);илиfinal Integer myField = 11;
Pshemo
58

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

Спецификация языка 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 компилировалась во время выполнения: если вы сбросите бит, он просто перекомпилируется с переменной как константой.
Билл К
4
@ Bill K - Нет, это не относится к JIT-компиляции. Файлы зависимых классов на самом деле будут содержать встроенные значения и не будут ссылаться на независимый класс. Это довольно простой эксперимент для тестирования; Я добавлю пример.
Эриксон
1
Как это соотносится с ответом @polygenelubricants, где он переопределяет Boolean.false? - но вы правы, я видел такое поведение, когда вещи не перекомпилировались правильно.
Билл К
26
@Bill K - в ответе полигенубрикантов поле не является постоянной времени компиляции. Это public static final Boolean FALSE = new Boolean(false)неpublic static final boolean FALSE = false
Эриксон
17

Немного любопытства из Спецификации языка 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

Стефан Марквалдер
источник
9

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

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

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

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

iirekm
источник
Когда я пытаюсь это сделать (JDK12), я получаю исключение: «Не удается установить окончательное поле ___».
Аарон Иба
@AaronIba Это больше не разрешено в Java 12+.
ягодичной
7

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

nndru
источник
Это гораздо более простое решение, чем принятый в настоящее время ответ
Берни
4
Это? Копирование одного метода звучит как более простое решение, чем импорт всей библиотеки (которая делает то же самое, что и метод, который вы будете копировать).
Эски
1
Не работает в Java 12+:java.lang.UnsupportedOperationException: In java 12+ final cannot be removed.
MrPowerGamerBR
6

В случае присутствия диспетчера безопасности, можно использовать 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;
});
Ванагасом
источник
1
Это также, кажется, не работает с Java 12+.
dan1st
да @ dan1st, ты прав! Пожалуйста, проверьте это для решения: stackoverflow.com/a/56043252/2546381
VanagaS
2

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

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

Томаш Залуски
источник
2

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

Какое-то имя
источник
0

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

 /**
 * @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

hasskell
источник
Вы видели этот пост?
Равиндра HV
Этот вопрос касается staticпоследнего поля, поэтому этот код не работает. setAccessible(true)работает только для установки полей окончательного экземпляра.
Radiodef
0

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

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

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

Филип Рего
источник
-4

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

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

thecoop
источник
4
Более того, он finalиграет особую роль в многопоточном выполнении - изменение finalзначений также нарушит модель памяти Java.
Петер Тёрёк
1
И поле, не объявленное, finalне должно быть объявлено static.
Том Хотин - снасть
2
@Tom: В общем, это, вероятно, правда, но я бы не поставил вне закона все статические изменяемые переменные.
кошка
7
@Tom: Вы когда-нибудь читали, почему синглтоны злые? Я сделал! Теперь я знаю, что они злые только на Яве. И только из-за доступности пользовательского загрузчика классов. И с тех пор как я все это знаю и не использую пользовательский загрузчик классов, я использую синглтоны без сожаления. И так же Scala, где синглтон - это первоклассная языковая особенность. То, что синглтоны являются злом, - это хорошо известный ложный миф .
Мартин
3
@ Мартин Я знаю, что ваш комментарий устарел, и, возможно, ваши взгляды уже изменились, но я подумал, что просто добавлю это: синглтоны - это зло по причинам, которые не имеют ничего общего с Java. Они добавляют скрытую сложность вашему коду. Кроме того, они могут сделать невозможным модульное тестирование, не зная также, что сначала необходимо настроить n синглетонов. Они являются противоположностью инъекции зависимости. Ваша команда может принять решение, что ловушки скрытой сложности не перевешивают удобство одиночных игр, но многие команды занимают противоположную позицию по уважительной причине.
раздавить