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

问题

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

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


#1 热门回答(57 赞)

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

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

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

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


#2 热门回答(47 赞)

这根本不可能。变量名称不在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或其他字段具有相同的引用,则会出现问题。
自问题澄清后编辑现在不必要


#3 热门回答(28 赞)

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

如果使用(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-lflag("打印行号和局部变量表")可以显示它们。

javap -l -c TestLocalVarNamesshows:

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.9TheLocalVariableTableAttribute:

LocalVariableTable属性是Code(§4.7.3)属性的可选可变长度属性。调试器可以使用它来确定方法执行期间给定局部变量的值。

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

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