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

Более общая функция для сортировки элементов с помощью jQuery:

$.fn.sortChildren = function (sortingFunction: any) {

    return this.each(function () {
        const children = $(this).children().get();
        children.sort(sortingFunction);
        $(this).append(children);
    });

};

Использование:

$(".clist").sortChildren((a, b) => a.dataset.sid > b.dataset.sid ? 1 : -1);
442
задан Hearen 21 March 2019 в 00:09
поделиться

4 ответа

Предполагая, что никакой 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, приведенный выше код выводит "Все верно".

На самом деле здесь сделано следующее:

  • Примитивные значения 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 Constant Expression
    • Маловероятно, что эта техника работает с примитивом 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";
    }
}

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

Подробнее о причинах такого поведения читайте здесь?

849
ответ дан 22 November 2019 в 23:05
поделиться

Даже несмотря на то, чтобы быть final поле может быть изменено за пределами статического инициализатора, и (по крайней мере, JVM HotSpot) выполнит превосходный байт-код.

проблема состоит в том, что компилятор Java не позволяет это, но это может быть легко обойдено с помощью objectweb.asm. Вот совершенно допустимый classfile, который передает проверку байт-кода и успешно загруженный и инициализированный под 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
ответ дан 22 November 2019 в 23:05
поделиться

Если значение, присвоенное статическому конечному логическому полю , известно во время компиляции, это константа . Поля примитивных или Тип 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 ( истина ) в стек (инструкция №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
53
ответ дан 22 November 2019 в 23:05
поделиться

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

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

-5
ответ дан 22 November 2019 в 23:05
поделиться
Другие вопросы по тегам:

Похожие вопросы: