Изменить частное статическое конечное поле с помощью отражения Java - PullRequest
433 голосов
/ 21 июля 2010

У меня есть класс с полем private static final, который, к сожалению, мне нужно изменить во время выполнения.

Используя отражение, я получаю эту ошибку: java.lang.IllegalAccessException: Can not set static final boolean field

IsЕсть ли способ изменить значение?

Field hack = WarpTransform2D.class.getDeclaredField("USE_HACK");
hack.setAccessible(true);
hack.set(null, true);

Ответы [ 10 ]

814 голосов
/ 21 июля 2010

Предполагая, что нет 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";
    }
}

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

Подробнее о том, почему это поведение , читайте ?

51 голосов
/ 21 июля 2010

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

* * * * * * * * * * * * * * * * * * * * * * *.постоянная переменная (§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
13 голосов
/ 21 ноября 2012

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

6 голосов
/ 07 июля 2015

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

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

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

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

5 голосов
/ 07 июня 2017

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

5 голосов
/ 25 марта 2017

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

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

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

0 голосов
/ 23 января 2019

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

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

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

0 голосов
/ 04 марта 2016

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

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

0 голосов
/ 21 июля 2010

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

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

...