问题
使用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
的-l
flag("打印行号和局部变量表")可以显示它们。
javap -l -c TestLocalVarNames
shows:
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.9TheLocalVariableTable
Attribute:
LocalVariableTable属性是Code(§4.7.3)属性的可选可变长度属性。调试器可以使用它来确定方法执行期间给定局部变量的值。
LocalVariableTable
存储每个插槽中变量的名称和类型,因此可以将它们与字节码相匹配。这是调试器可以执行"评估表达式"的方式。
正如埃里克森所说,通过正常反射无法访问此表。如果你仍然决定这样做,我相信Java Platform Debugger Architecture (JPDA)会帮助(但我自己从未使用过它)。