web-dev-qa-db-ja.com

Java Reflection:変数の名前を取得する方法は?

Java Reflectionを使用すると、ローカル変数の名前を取得できますか?たとえば、これがある場合:

Foo b = new Foo();
Foo a = new Foo();
Foo r = new Foo();

次のように、これらの変数の名前を見つけることができるメソッドを実装することは可能ですか?

public void baz(Foo... foos)
{
    for (Foo foo: foos) {
        // Print the name of each foo - b, a, and r
        System.out.println(***); 
    }
}

編集:この質問は 関数に渡された変数の名前を見つける方法はJavaにありますか? inリフレクションを使用してローカル変数の名前を決定できるかどうかについてより純粋に質問するのに対し、他の質問(受け入れられた回答を含む)は変数の値をテストすることに焦点を当てています

128
David Koelle

Java 8の時点で、いくつかのローカル変数名情報はリフレクションを通じて利用可能です。以下の "Update"セクションを参照してください。

多くの場合、完全な情報はクラスファイルに保存されます。コンパイル時の最適化の1つは、それを削除してスペースを節約することです(そして、難読化を提供します)。ただし、存在する場合、各メソッドには、ローカル変数のタイプと名前、およびスコープ内にある命令の範囲をリストするローカル変数テーブル属性があります。

ASM のようなバイトコードエンジニアリングライブラリを使用すると、実行時にこの情報を検査できます。この情報を必要とするために私が考えることができる唯一の合理的な場所は、開発ツール内にあるため、バイトコードエンジニアリングは他の目的にも役立つ可能性があります。


更新:これに対する限定サポートは Java 8.に追加 パラメータ(ローカルの特別なクラス変数)名前がリフレクション経由で利用可能になりました。他の目的の中でも、これは、依存性注入コンテナーで使用される@ParameterName注釈を置き換えるのに役立ちます。

62
erickson

notはまったく不可能です。変数名はJava内で通信されません(また、コンパイラーの最適化により削除される場合があります)。

編集(コメントに関連):

関数のパラメーターとして使用する必要があるという考えから戻った場合、代替手段があります(これは使用しません-以下を参照)。

public void printFieldNames(Object obj, Foo... foos) {
    List<Foo> fooList = Arrays.asList(foos);
    for(Field field : obj.getClass().getFields()) {
         if(fooList.contains(field.get()) {
              System.out.println(field.getName());
         }
    }
}

a == b, a == r, or b == rまたは同じ参照を持つ他のフィールドがある場合、問題が発生します。

質問が明確になったため、編集は不要になりました

48

編集:2つの以前の回答が削除されました。1つは編集前の質問に答えるためのもので、もう1つは絶対に間違っていない場合は少なくともそれに近いものです。

javac -g)のデバッグ情報を使用してコンパイルする場合、ローカル変数の名前は.classファイルに保持されます。たとえば、次の単純なクラスを使用します。

class TestLocalVarNames {
    public String aMethod(int arg) {
        String local1 = "a string";
        StringBuilder local2 = new StringBuilder();
        return local2.append(local1).append(arg).toString();
    }
}

javac -g:vars TestLocalVarNames.Javaでコンパイルした後、ローカル変数の名前は.classファイルになりました。 javap-lフラグ(「行番号とローカル変数テーブルの印刷」)はそれらを表示できます。

javap -l -c TestLocalVarNamesの表示:

class TestLocalVarNames extends Java.lang.Object{
TestLocalVarNames();
  Code:
   0:   aload_0
   1:   invokespecial   #1; //Method Java/lang/Object."<init>":()V
   4:   return

  LocalVariableTable:
   Start  Length  Slot  Name   Signature
   0      5      0    this       LTestLocalVarNames;

public Java.lang.String aMethod(int);
  Code:
   0:   ldc     #2; //String a string
   2:   astore_2
   3:   new     #3; //class Java/lang/StringBuilder
   6:   dup
   7:   invokespecial   #4; //Method Java/lang/StringBuilder."<init>":()V
   10:  astore_3
   11:  aload_3
   12:  aload_2
   13:  invokevirtual   #5; //Method Java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
   16:  iload_1
   17:  invokevirtual   #6; //Method Java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
   20:  invokevirtual   #7; //Method Java/lang/StringBuilder.toString:()Ljava/lang/String;
   23:  areturn

  LocalVariableTable:
   Start  Length  Slot  Name   Signature
   0      24      0    this       LTestLocalVarNames;
   0      24      1    arg       I
   3      21      2    local1       Ljava/lang/String;
   11      13      3    local2       Ljava/lang/StringBuilder;
}

VM spec は、ここで見ているものを説明しています。

§4.7.9LocalVariableTable属性

LocalVariableTable属性は、Code(§4.7.3)属性のオプションの可変長属性です。デバッガがメソッドの実行中に特定のローカル変数の値を決定するために使用できます。

LocalVariableTableには、各スロットの変数の名前とタイプが格納されているため、バイトコードと一致させることができます。これは、デバッガーが「式を評価」する方法です。

しかし、エリクソンが言ったように、通常のリフレクションを介してこのテーブルにアクセスする方法はありません。それでもこれを行うことに決めている場合、 Java Platform Debugger Architecture(JPDA) が役立つと思います(しかし、私は自分で使ったことはありません)。

29
Michael Myers
import Java.lang.reflect.Field;


public class test {

 public int i = 5;

 public Integer test = 5;

 public String omghi = "der";

 public static String testStatic = "THIS IS STATIC";

 public static void main(String[] args) throws IllegalArgumentException, IllegalAccessException {
  test t = new test();
  for(Field f : t.getClass().getFields()) {
   System.out.println(f.getGenericType() +" "+f.getName() + " = " + f.get(t));
  }
 }

}
14
Peeter

このようにすることができます:

Field[] fields = YourClass.class.getDeclaredFields();
//gives no of fields
System.out.println(fields.length);         
for (Field field : fields) {
    //gives the names of the fields
    System.out.println(field.getName());   
}
8
tinker_fairy

必要なのは、フィールドの配列を作成し、次に示すように必要なクラスに設定することだけです。

Field fld[] = (class name).class.getDeclaredFields();   
for(Field x : fld)
{System.out.println(x);}

たとえば、あなたがした場合

Field fld[] = Integer.class.getDeclaredFields();
          for(Field x : fld)
          {System.out.println(x);}

取得します

public static final int Java.lang.Integer.MIN_VALUE
public static final int Java.lang.Integer.MAX_VALUE
public static final Java.lang.Class Java.lang.Integer.TYPE
static final char[] Java.lang.Integer.digits
static final char[] Java.lang.Integer.DigitTens
static final char[] Java.lang.Integer.DigitOnes
static final int[] Java.lang.Integer.sizeTable
private static Java.lang.String Java.lang.Integer.integerCacheHighPropValue
private final int Java.lang.Integer.value
public static final int Java.lang.Integer.SIZE
private static final long Java.lang.Integer.serialVersionUID
0