54

簡単なテストケースから始めましょう:

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

出力として何が印刷されるかは、誰でも気になるところです (驚きをすぐに台無しにしないように、一番下に表示されています)。

質問は次のとおりです。

  1. プリミティブとラップされた整数の動作が異なるのはなぜですか?
  2. リフレクティブ アクセスと直接アクセスで異なる結果が返されるのはなぜですか?
  3. 私を最も悩ませているのは、なぜ String はプリミティブのように動作し、 のように動作intしないのIntegerですか?

結果 (Java 1.5):

reflection: primitiveInt = 84
direct: primitiveInt = 42
reflection: wrappedInt = 84
direct: wrappedInt = 84
reflection: stringValue = 84
direct: stringValue = 42
4

5 に答える 5

22

コンパイル時の定数は (javac コンパイル時に) インライン化されます。JLS を参照してください。特に、15.28 では定数式が定義されており、13.4.9 ではバイナリ互換性または最終フィールドと定数について説明されています。

フィールドを非 final にするか、非コンパイル時定数を割り当てると、値はインライン化されません。例えば:

private final String stringValue = null!=null?"": "42";

于 2009-10-23T18:39:23.277 に答える
10

私の意見では、これはさらに悪いことです: 同僚が次の面白いことを指摘しました:

@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 の間の値の値のみを変更できます)。

于 2011-02-08T08:48:45.673 に答える
8

Reflectionのset(..)メソッドはFieldAccessorsで機能します。

のためintUnsafeQualifiedIntegerFieldAccessorImpl、そのスーパークラスは、フィールドが両方readOnlyある場合にのみプロパティがtrueであると定義します。 staticfinal

したがって、最初に質問されていない質問に答えるために-これ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ます)IntegerString

それはいくつかの興味深い結果をもたらします:

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-133を引用しています:

フィールド宣言で最終フィールドがコンパイル時定数に初期化されている場合、その最終フィールドの使用はコンパイル時にコンパイル時定数に置き換えられるため、最終フィールドへの変更が観察されない場合があります。

第9章では、この質問で観察された詳細について説明します。

finalまた、フィールドの変更はオブジェクトの初期化の直後にのみ発生することになっているため、この動作はそれほど予期しないものではないことがわかります。

于 2010-01-21T09:02:25.630 に答える
1

これは答えではありませんが、別の混乱点を引き起こします。

問題がコンパイル時の評価なのか、リフレクションによって実際に Java がfinalキーワードを回避できるようになったのかを確認したかったのです。ここにテストプログラムがあります。追加したのは getter 呼び出しの別のセットだけだったので、各呼び出しの前後に 1 つずつありchangeField()ます。

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() への直接呼び出しが変更されるのはなぜですか?

于 2009-10-23T22:11:28.713 に答える
0

これには回避策があります。静的 {} ブロックにプライベート静的最終フィールドの値を設定すると、フィールドがインライン化されないため機能します。

private static final String MY_FIELD;

static {
    MY_FIELD = "SomeText"
}

...

Field field = VisitorId.class.getDeclaredField("MY_FIELD");

field.setAccessible(true);
field.set(field, "fakeText");
于 2013-12-31T13:34:50.600 に答える