簡単なテストケースから始めましょう:
import Java.lang.reflect.Field;
public class Test {
private final int primitiveInt = 42;
private final Integer wrappedInt = 42;
private final String stringValue = "42";
public int getPrimitiveInt() { return this.primitiveInt; }
public int getWrappedInt() { return this.wrappedInt; }
public String getStringValue() { return this.stringValue; }
public void changeField(String name, Object value) throws IllegalAccessException, NoSuchFieldException {
Field field = Test.class.getDeclaredField(name);
field.setAccessible(true);
field.set(this, value);
System.out.println("reflection: " + name + " = " + field.get(this));
}
public static void main(String[] args) throws IllegalAccessException, NoSuchFieldException {
Test test = new Test();
test.changeField("primitiveInt", 84);
System.out.println("direct: primitiveInt = " + test.getPrimitiveInt());
test.changeField("wrappedInt", 84);
System.out.println("direct: wrappedInt = " + test.getWrappedInt());
test.changeField("stringValue", "84");
System.out.println("direct: stringValue = " + test.getStringValue());
}
}
誰もが出力として何が印刷されるかを推測することに気を配っています(驚きをすぐに台無しにしないように下部に示されています)。
質問は次のとおりです。
int
のように動作し、Integer
のように動作しないのはなぜですか。結果(Java 1.5):
reflection: primitiveInt = 84
direct: primitiveInt = 42
reflection: wrappedInt = 84
direct: wrappedInt = 84
reflection: stringValue = 84
direct: stringValue = 42
コンパイル時定数はインライン化されます(javacコンパイル時)。 JLSを参照してください。特に、15.28は定数式を定義し、13.4.9はバイナリ互換性または最終フィールドと定数について説明しています。
フィールドを非最終にするか、非コンパイル時定数を割り当てると、値はインライン化されません。例えば:
private final String stringValue = null!= null? "": "42";
私の意見では、これはさらに悪いことです。同僚が次の面白いことを指摘しました。
@Test public void testInteger() throws SecurityException, NoSuchFieldException, IllegalArgumentException, IllegalAccessException {
Field value = Integer.class.getDeclaredField("value");
value.setAccessible(true);
Integer manipulatedInt = Integer.valueOf(7);
value.setInt(manipulatedInt, 666);
Integer testInt = Integer.valueOf(7);
System.out.println(testInt.toString());
}
これにより、実行しているJVM全体の動作を変更できます(もちろん、-127から127の間の値の値のみを変更できます)。
Reflectionのset(..)
メソッドはFieldAccessor
sで機能します。
int
の場合、UnsafeQualifiedIntegerFieldAccessorImpl
を取得します。そのスーパークラスは、フィールドがbothreadOnly
とstatic
の場合にのみ、final
プロパティをtrueに定義します。
したがって、最初に質問されていない質問に答えるには、これがfinal
が例外なく変更される理由です。
UnsafeQualifiedFieldAccessor
のすべてのサブクラスは、_Sun.misc.Unsafe
_クラスを使用して値を取得します。メソッドはすべてnative
ですが、名前はgetVolatileInt(..)
とgetInt(..)
(それぞれgetVolatileObject(..)
とgetObject(..)
)です。前述のアクセサーは「揮発性」バージョンを使用します。揮発性でないバージョンを追加すると、次のようになります。
_System.out.println("reflection: non-volatile primitiveInt = "
unsafe.getInt(test, (long) unsafe.fieldOffset(getField("primitiveInt"))));
_
(unsafe
はリフレクションによってインスタンス化されます-それ以外の場合は許可されません)(そして私はgetObject
とInteger
に対してString
を呼び出します)
それはいくつかの興味深い結果をもたらします:
_reflection: primitiveInt = 84
direct: primitiveInt = 42
reflection: non-volatile primitiveInt = 84
reflection: wrappedInt = 84
direct: wrappedInt = 84
reflection: non-volatile wrappedInt = 84
reflection: stringValue = 84
direct: stringValue = 42
reflection: non-volatile stringValue = 84
_
この時点で私は思い出します javaspecialists.euの記事 関連事項について議論しました。引用します JSR-1 :
フィールド宣言で最終フィールドがコンパイル時定数に初期化されている場合、その最終フィールドの使用はコンパイル時にコンパイル時定数に置き換えられるため、最終フィールドへの変更が観察されない場合があります。
第9章では、この質問で観察された詳細について説明します。
final
フィールドの変更は、オブジェクトの初期化の直後にのみ発生することになっているため、この動作はそれほど予期しないものではないことがわかります。
これは答えではありませんが、別の混乱を招きます。
問題がコンパイル時の評価なのか、それともリフレクションによって実際にJavaがfinal
キーワードを回避できるのか)を確認したかったのです。これがテストプログラムです。追加したのは別のゲッター呼び出しのセットなので、各changeField()
呼び出しの前後に1つあります。
package com.example.gotchas;
import Java.lang.reflect.Field;
public class MostlyFinal {
private final int primitiveInt = 42;
private final Integer wrappedInt = 42;
private final String stringValue = "42";
public int getPrimitiveInt() { return this.primitiveInt; }
public int getWrappedInt() { return this.wrappedInt; }
public String getStringValue() { return this.stringValue; }
public void changeField(String name, Object value) throws IllegalAccessException, NoSuchFieldException {
Field field = MostlyFinal.class.getDeclaredField(name);
field.setAccessible(true);
field.set(this, value);
System.out.println("reflection: " + name + " = " + field.get(this));
}
public static void main(String[] args) throws IllegalAccessException, NoSuchFieldException {
MostlyFinal test = new MostlyFinal();
System.out.println("direct: primitiveInt = " + test.getPrimitiveInt());
test.changeField("primitiveInt", 84);
System.out.println("direct: primitiveInt = " + test.getPrimitiveInt());
System.out.println();
System.out.println("direct: wrappedInt = " + test.getWrappedInt());
test.changeField("wrappedInt", 84);
System.out.println("direct: wrappedInt = " + test.getWrappedInt());
System.out.println();
System.out.println("direct: stringValue = " + test.getStringValue());
test.changeField("stringValue", "84");
System.out.println("direct: stringValue = " + test.getStringValue());
}
}
これが私が得る出力です(Eclipseの下では、Java 1.6)
direct: primitiveInt = 42
reflection: primitiveInt = 84
direct: primitiveInt = 42
direct: wrappedInt = 42
reflection: wrappedInt = 84
direct: wrappedInt = 84
direct: stringValue = 42
reflection: stringValue = 84
direct: stringValue = 42
GetWrappedInt()への直接呼び出しが変更されるのはなぜですか?
これには回避策があります。 static {}ブロックに提出されたprivatestatic finalの値を設定すると、fileldがインライン化されないため、機能します。
private static final String MY_FIELD;
static {
MY_FIELD = "SomeText"
}
...
Field field = VisitorId.class.getDeclaredField("MY_FIELD");
field.setAccessible(true);
field.set(field, "fakeText");