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);

4
そのような悪い考え。ソースを取得して、代わりに再コンパイル(または逆コンパイル/再コンパイル)しようと思います。
ビルK

System.outはpublic static finalフィールドですが、変更することもできます。
評判の悪い2010

19
@irreputable System.out/in/errは非常に「特別」であるため、Javaメモリモデルはそれらについて特別に言及する必要があります。従うべき例ではありません。
トム・ホーティン-10

8
まあ私のポイントは、責任のあるlibが次のリリースで変更を行うまでアプリを機能させるためにハッキングを見つけることです。これにより、ハッキングする必要はもうありません...
10

1
10年前の@Bill K:再コンパイルするのは素晴らしいことですが、展開されたシステム上にあるので、展開されたアプリを更新できるようになるまでパッチを適用するだけです!
ビルK

回答:


888

これが原因で問題が発生しないと想定するとSecurityManager、を使用setAccessibleprivateて修飾子を回避およびリセットし、を削除して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"
   }
}

no SecurityExceptionがスローされると仮定すると、上記のコードはを出力し"Everything is true"ます。

ここで実際に行われることは次のとおりです。

  • プリミティブbooleantruefalsein mainは参照型Boolean「定数」にオートボックス化されBoolean.TRUEBoolean.FALSE
  • 反射が変化するために使用さpublic static final Boolean.FALSEを指すためBooleanで参照しますBoolean.TRUE
  • その結果、その後a falseがにオートボックス化されるときはいつでも、によってBoolean.FALSE参照されるものと同じものを参照Booleanします。Boolean.TRUE
  • "false"今だったすべてが"true"

関連する質問


注意事項

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

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

デシリアライゼーションなど、場合によっては、システムはfinal構築後にオブジェクトのフィールドを変更する必要があります。finalフィールドは、リフレクションおよびその他の実装に依存する手段を介して変更できます。これが妥当なセマンティクスを持つ唯一のパターンは、オブジェクトが構築finalされ、オブジェクトのフィールドが更新されるパターンです。オブジェクトfinalfinalフィールドに対するすべての更新が完了するまで、オブジェクトを他のスレッドから見えるようにしたり、フィールドを読み取ったりしてはなりません。finalフィールドのフリーズは、フィールドfinalが設定されているコンストラクタの最後と、finalリフレクションまたはその他の特別なメカニズムによるフィールドの各変更の直後の両方で発生します。

それでも、いくつかの複雑な問題があります。場合finalフィールドはフィールド宣言にコンパイル時定数に初期化され、変更finalその使用するのでフィールドは、観察されないことがfinalフィールドがコンパイル時定数でコンパイル時に置き換えられます。

別の問題は、仕様がfinalフィールドの積極的な最適化を許可することです。スレッド内ではfinal、コンストラクターで発生しない最終フィールドの変更を含むフィールドの読み取りを並べ替えることができます。

こちらもご覧ください

  • JLS 15.28定数式
    • この手法がプリミティブprivate static final booleanで機能することはほとんどありません。コンパイル時の定数としてインライン化できるため、「新しい」値を監視できない可能性があるためです。

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

本質的に、

field.getModifiers() & ~Modifier.FINAL

Modifier.FINALfromに対応するビットをオフにしますfield.getModifiers()&はビット単位のANDであり、~ビット単位の補数です。

こちらもご覧ください


定数式を覚える

まだこれを解決できないのですか?私がやったようにうつ病に陥っていますか?あなたのコードはこのように見えますか?

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

この回答のコメント、特に@Pshemoのコメントを読んだとき、定数式は別の方法で処理されるため、変更することは不可能であることを思い出しました。したがって、次のようにコードを変更する必要があります。

public class A {
    private final String myVar;

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

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

この動作の詳細については、これをお読みください


41
@thecoop、@HalfBrian:そこにこれがあることは間違いないですEXTREMELY EVILは、この例は、設計によって選ばれました。私の答えは、状況によっては、これがどのようにして可能であるかを示すだけです。私が考えることができる最も嫌な例は、おそらく人々がテクニックに恋するのではなく即座に嫌悪されることを期待して慎重に選択されたものです。
polygenelubricants

59
ヨ、ダグ。反射が好きだと聞いたので、フィールドに反射して、反射しながら反射できるようにしました。
マシューFlaschen

11
Boolean.FALSEはプライベートではないことに注意してください。これは本当に「プライベートの最終静的」メンバーで機能しますか?
mgaert 2013

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 = 11use 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を思い出しました。ビットをリセットすると、定数ではなく変数として単に再コンパイルされます。
ビルK

4
@Bill K-いいえ、これはJITコンパイルを指すものではありません。依存クラスファイルには実際にはインライン化された値が含まれ、独立クラスへの参照は含まれません。テストするのは非常に簡単な実験です。例を追加します。
エリクソン2010

1
これが@polygenelubricantsのBoolean.falseを再定義する回答にどのように反応するのですか?-しかし、あなたは正しいです。
ビルK

26
@Bill K-多重潤滑剤の答えでは、フィールドはコンパイル時定数ではありません。そうでpublic static final Boolean FALSE = new Boolean(false)はありませんpublic static final boolean FALSE = false
エリクソン2010

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、以前のソリューションでは見落としていた問題を修正しました。ただし、これは他の適切な解決策がない場合にのみ、慎重に使用してください。


これ(JDK12)を試すと、「最後の___フィールドを設定できません」という例外が発生します。
アーロン・伊庭

@AaronIba Java 12以降では使用できなくなりました。
NateS

7

トップランクの回答と一緒に、少し単純なアプローチを使用できます。ApacheコモンズFieldUtilsクラスには、処理を実行できる特定のメソッドがすでにあります。方法をご覧くださいFieldUtils.removeFinalModifier。ターゲットフィールドインスタンスとアクセシビリティ強制フラグを指定する必要があります(非パブリックフィールドを使用する場合)。詳細については、こちらをご覧ください


これは、現在受け入れられている回答よりもはるかに簡単な解決策です
Bernie

4
それは...ですか?1つのメソッドをコピーすることは、ライブラリ全体をインポートする(コピーするメソッドと同じことを行う)よりも簡単なソリューションのように聞こえます。
エスキー

1
Java 12以降では機能しません:java.lang.UnsupportedOperationException: In java 12+ final cannot be removed.
MrPowerGamerBR

6

Security Managerが存在する場合は、 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.UnsafeQualifiedStaticObjectFieldAccessorImplsun.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がチェックする唯一の制限はfinalfinalフィールドが宣言されているクラスの外部でフィールドを変更しないことです。


0

最後の変数をリフレクションで、または実行時に変更できる場合は、インタビューの質問の1つでその質問を見ただけです。本当に興味を持ったので、私が次のようになりました:

 /**
 * @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();
    }
}

最終的なString変数を持ついくつかの単純なクラス。したがって、メインクラスで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


この投稿を見ましたか?
Ravindra HV 2016

この質問は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、最初にそれを宣言しないことです。


4
さらに、finalマルチスレッド実行で特別な役割を果たしfinalます- 値を変更すると、Javaメモリモデルも壊れます。
ペーテルTörök

1
また、宣言されてfinalいないフィールドは宣言しないでくださいstatic
トム・ホーティン-タックライン

2
@Tom:一般的にはそれはおそらく本当ですが、私はすべての静的な可変変数を禁止ません。
bcat

7
@トム:シングルトンが悪である理由を読んだことはありますか?やった!今、私は彼らがJavaでのみ悪であることを知っています。また、ユーザー定義のクラスローダーが利用できるためです。そして、私はこれをすべて知っており、ユーザー定義のクラスローダーを使用していないので、後悔せずにシングルトンを使用しています。シングルトンがファーストクラスの言語機能であるScalaも同様です。シングルトンが悪であるということは、よく知られている偽の神話です。
マーティン

3
@Martin私はあなたのコメントが古いことを知っています、そしておそらくあなたの見解は今までに変わったかもしれませんが、私はこれを追加したいと思いました:シングルトンはJavaとは何の関係もない理由で悪です。それらはコードに隠された複雑さを追加します。さらに、n個のシングルトンも最初に構成する必要があることを知らなければ、単体テストを不可能にする可能性があります。それらは依存性注入のアンチテーゼです。あなたのチームは、複雑さ隠されていることの落とし穴がシングルトンの利便性を上回らないという決定を下すかもしれませんが、多くのチームは正当な理由で反対のスタンスをとっています。
クラッシュ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.