1. ホーム
  2. java

[解決済み] Javaリフレクションを用いた静的なプライベートfinalフィールドの変更

2022-03-19 10:58:52

質問

を持つクラスがあります。 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);

解決方法は?

ないものとします。 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" .

ここで実際に行われているのは、次のようなことです。

  • プリミティブな booleantruefalsemain は参照型に自動変換されます。 Boolean "定数" Boolean.TRUEBoolean.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 なぜなら、コンパイル時に定数としてインライン化されるため、 "new"の値が観測できない可能性があるからです。

付録 ビット単位の操作について

本質的に

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";
    }
}

クラスの所有者でない場合は... 私はあなたを感じる!

なぜこのような動作になるのか、詳しくは これを読む ?