web-dev-qa-db-ja.com

Javaリフレクションを使用してprivate static final fieldを変更する

残念ながら、実行時に変更する必要がある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);
428
fixitagain

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 values falseおよびmainは、参照型Boolean "定数" Boolean.TRUEおよびBoolean.FALSEに自動ボクシングされます。
  • リフレクションは public static final Boolean.FALSEBoolean.TRUEによって参照されるBooleanを参照するように変更するために使用されます。
  • その結果、その後、falseBoolean.FALSEに自動ボクシングされるときはいつでも、それはBoolean.TRUEによって参照されるものと同じBooleanを参照します。
  • "false"だったものはすべて"true"です

関連する質問


注意事項

このようなことをするときはいつでも細心の注意を払うべきです。 SecurityManagerが存在している可能性があるため機能しない可能性がありますが、使用パターンによっては機能しない可能性があります。

JLS 17.5.3最終フィールドのその後の変更

逆シリアル化など、場合によっては、システムは構築後にオブジェクトのfinalフィールドを変更する必要があります。 finalフィールドはリフレクションやその他の実装依存の方法で変更できます。これが合理的な意味を持つ唯一のパターンは、オブジェクトが構築され、それからオブジェクトのfinalフィールドが更新されるというものです。オブジェクトのfinalフィールドへの更新がすべて完了するまで、オブジェクトを他のスレッドから見えるようにしたり、finalフィールドを読み取ったりしないでください。 finalフィールドのフリーズは、finalフィールドが設定されているコンストラクターの最後と、リフレクションまたはその他の特別なメカニズムによるfinalフィールドの各変更の直後の両方で発生します。

それでも、多くの問題があります。フィールド宣言内でfinalフィールドがコンパイル時定数に初期化されている場合、finalフィールドの使用はコンパイル時に置き換えられるため、finalフィールドへの変更は行われない可能性があります。コンパイル時定数で。

もう1つの問題は、この仕様ではfinalフィールドを積極的に最適化できることです。スレッド内では、finalフィールドの読み取りを、コンストラクター内で行われない最終フィールドの変更で並べ替えることができます。

また見なさい

  • JLS 15.28定数式
    • このテクニックがプリミティブのprivate static final booleanでうまくいくとは考えにくいです。なぜならそれはコンパイル時定数としてインライン化可能であり、したがって「新しい」値は観察できないかもしれないからです。

付録:ビット操作について

基本的に

field.getModifiers() & ~Modifier.FINAL

field.getModifiers()からModifier.FINALに対応するビットをオフにします。 &はビットごとのandで、~はビットごとの補数です。

また見なさい


定数式を覚えておく

それでも解決できないのか、私がやったようにうつ病に陥ってしまった?あなたのコードはこんな感じですか?

public class A {
    private final String myVar = "Some Value";
}

この回答に対するコメント、特に@Pshemoによるコメントを読んで、 定数式 は別のものとして扱われるので、不可能修正することはできません。したがって、コードを次のように変更する必要があります。

public class A {
    private final String myVar;

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

あなたがそのクラスのオーナーではないなら...私はあなたを感じます!

なぜこの振る舞い がこれを読むのか

806

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
50
erickson

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

13

私はまたそれを joorライブラリ と統合しました

ただ使う

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

また、私はoverrideに関する問題を修正しましたが、これまでの解決策では見逃しているようです。ただし、他に良い解決策がない場合に限り、これを非常に慎重に使用してください。

6
iirekm

セキュリティマネージャが存在する場合、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;
});
5
VanagaS

トップランクの答えと一緒にあなたは少し単純なアプローチを使うかもしれません。 Apache commonsのFieldUtilsクラスには、すでにそのことができる特定のメソッドがあります。 FieldUtils.removeFinalModifierメソッドを見てください。ターゲットフィールドのインスタンスとアクセシビリティ強制フラグを指定する必要があります(非パブリックフィールドでプレイする場合)。あなたがここで見つけることができるより多くの情報。

5
nndru

受け入れられた答えはJDK 1.8u91に配置されるまで私のために働きました。 setFinalStaticメソッドを呼び出す前にリフレクションを介して値を読み取ったときに、field.set(null, newValue);行で失敗することに気付きました。

おそらく、この読み込みによって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
Some Name

あなたの分野が単に私的であるならば、あなたはこれをすることができます:

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

noSuchFieldExceptionをスローおよび処理します。

0
Philip Rego