Java反射:如何获取变量的名称?

时间:2009-04-13 15:17:48

标签: 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(***); 
    }
}

编辑:这个问题与Is there a way in Java to find the name of the variable that was passed to a function?的不同之处在于它更纯粹地询问是否可以使用反射来确定局部变量的名称,而另一个问题(包括接受的答案) )更专注于测试变量的值。

8 个答案:

答案 0 :(得分:62)

从Java 8开始,可以通过反射获得一些局部变量名称信息。请参阅下面的“更新”部分。

完整信息通常存储在类文件中。一个编译时优化是删除它,节省空间(并提供一些模糊处理)。但是,当它存在时,每个方法都有一个局部变量表属性,该属性列出局部变量的类型和名称,以及它们在范围内的指令范围。

ASM这样的字节码工程库可能允许您在运行时检查此信息。我需要这个信息的唯一合理的地方是开发工具,所以字节码工程也可能对其他用途有用。


更新:对此的有限支持是added to Java 8.现在可以通过反射获得参数(一类特殊的局部变量)名称。除此之外,这可以帮助替换依赖注入容器使用的@ParameterName注释。

答案 1 :(得分:48)

根本不可能 。变量名不在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或其他字段具有相同的引用,则会出现问题。

自问题澄清后,编辑现在没必要了

答案 2 :(得分:29)

编辑:删除了之前的两个答案,一个用于回答编辑前的问题,一个用于存在,如果不是绝对错误,至少接近它。

如果使用(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的{​​{1}}标记(“打印行号和局部变量表”)可以显示它们。

-l显示:

javap -l -c TestLocalVarNames

VM spec解释了我们在这里看到的内容:

§4.7.9 The LocalVariableTable Attribute

  

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; } 属性是LocalVariableTable(§4.7.3)属性的可选变长属性。调试器可以使用它来确定方法执行期间给定局部变量的值。

Code存储每个插槽中变量的名称和类型,因此可以将它们与字节码相匹配。这是调试器可以执行“评估表达式”的方式。

正如埃里克森所说,但是没有办法通过正常反射来访问这个表。如果您仍然决定这样做,我相信Java Platform Debugger Architecture (JPDA)会有所帮助(但我自己从未使用过它)。

答案 3 :(得分:14)

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

}

答案 4 :(得分:8)

你可以这样做:

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

答案 5 :(得分:0)

您需要做的就是创建一个字段数组,然后将其设置为您想要的类,如下所示。

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

答案 6 :(得分:0)

更新@Marcel Jackwerth对一般问题的回答。

并且仅使用class属性, 无法使用方法变量。

    /**
     * get variable name as string
     * only work with class attributes
     * not work with method variable
     *
     * @param headClass variable name space
     * @param vars      object variable
     * @throws IllegalAccessException
     */
    public static void printFieldNames(Object headClass, Object... vars) throws IllegalAccessException {
        List<Object> fooList = Arrays.asList(vars);
        for (Field field : headClass.getClass().getFields()) {
            if (fooList.contains(field.get(headClass))) {
                System.out.println(field.getGenericType() + " " + field.getName() + " = " + field.get(headClass));
            }
        }
    }

答案 7 :(得分:-1)

见这个例子:

PersonneTest pt=new PersonneTest();
System.out.println(pt.getClass().getDeclaredFields().length);
Field[]x=pt.getClass().getDeclaredFields();
System.out.println(x[1].getName());