残念ながら、実行時に変更する必要がある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"
を出力します。
ここで実際に行われていることは以下のとおりです。
boolean
内のプリミティブtrue
values false
およびmain
は、参照型Boolean
"定数" Boolean.TRUE
およびBoolean.FALSE
に自動ボクシングされます。public static final Boolean.FALSE
をBoolean.TRUE
によって参照されるBoolean
を参照するように変更するために使用されます。false
がBoolean.FALSE
に自動ボクシングされるときはいつでも、それはBoolean.TRUE
によって参照されるものと同じBoolean
を参照します。"false"
だったものはすべて"true"
ですstatic final File.separatorChar
を変更するInteger
のキャッシュをめちゃくちゃにしたり、String
を変更するなどの例があります。このようなことをするときはいつでも細心の注意を払うべきです。 SecurityManager
が存在している可能性があるため機能しない可能性がありますが、使用パターンによっては機能しない可能性があります。
逆シリアル化など、場合によっては、システムは構築後にオブジェクトの
final
フィールドを変更する必要があります。final
フィールドはリフレクションやその他の実装依存の方法で変更できます。これが合理的な意味を持つ唯一のパターンは、オブジェクトが構築され、それからオブジェクトのfinal
フィールドが更新されるというものです。オブジェクトのfinal
フィールドへの更新がすべて完了するまで、オブジェクトを他のスレッドから見えるようにしたり、final
フィールドを読み取ったりしないでください。final
フィールドのフリーズは、final
フィールドが設定されているコンストラクターの最後と、リフレクションまたはその他の特別なメカニズムによるfinal
フィールドの各変更の直後の両方で発生します。それでも、多くの問題があります。フィールド宣言内で
final
フィールドがコンパイル時定数に初期化されている場合、final
フィールドの使用はコンパイル時に置き換えられるため、final
フィールドへの変更は行われない可能性があります。コンパイル時定数で。もう1つの問題は、この仕様では
final
フィールドを積極的に最適化できることです。スレッド内では、final
フィールドの読み取りを、コンストラクター内で行われない最終フィールドの変更で並べ替えることができます。
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";
}
}
あなたがそのクラスのオーナーではないなら...私はあなたを感じます!
なぜこの振る舞い がこれを読むのか
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言語仕様、第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
私はまたそれを joorライブラリ と統合しました
ただ使う
Reflect.on(yourObject).set("finalFieldName", finalFieldValue);
また、私はoverride
に関する問題を修正しましたが、これまでの解決策では見逃しているようです。ただし、他に良い解決策がない場合に限り、これを非常に慎重に使用してください。
セキュリティマネージャが存在する場合、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;
});
トップランクの答えと一緒にあなたは少し単純なアプローチを使うかもしれません。 Apache commonsのFieldUtils
クラスには、すでにそのことができる特定のメソッドがあります。 FieldUtils.removeFinalModifier
メソッドを見てください。ターゲットフィールドのインスタンスとアクセシビリティ強制フラグを指定する必要があります(非パブリックフィールドでプレイする場合)。あなたがここで見つけることができるより多くの情報。
受け入れられた答えは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;
} ...
しかしながら、一般的な場合にはこれでは十分ではないでしょう。
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
フィールドを変更しないことです。
あなたの分野が単に私的であるならば、あなたはこれをすることができます:
MyClass myClass= new MyClass();
Field aField= myClass.getClass().getDeclaredField("someField");
aField.setAccessible(true);
aField.set(myClass, "newValueForAString");
noSuchFieldExceptionをスローおよび処理します。