Java 反射: 如何得到一个变量的名称?

使用 Java 反射,是否可以获得局部变量的名称?例如,如果我有这个:

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 中有没有找到传递给函数的变量名的方法?,因为它更纯粹地询问是否可以使用反射来确定局部变量的名称,而另一个问题(包括公认的答案)更侧重于测试变量的值。

304925 次浏览

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

现在没有必要编辑,因为问题得到澄清

(编辑: 删除了以前的两个答案,一个用于回答编辑之前的问题,另一个用于回答即使不是绝对错误,至少也是接近正确答案的问题。)

如果使用(javac -g)上的调试信息进行编译,则局部变量的名称保存在。类别档案。例如,以下面这个简单的类为例:

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编译之后,局部变量的名称现在位于。类别档案。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;
}

虚拟机规格解释了我们在这里看到的:

4.7.9 LocalVariableTable属性 :

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

LocalVariableTable在每个槽中存储变量的名称和类型,因此可以将它们与字节码进行匹配。这就是调试器执行“求值表达式”的方法。

但正如 Erickson 所说,通过正常反射无法访问这张表。如果你仍然决心这样做,我相信 JPDA (JPDA)将有所帮助(但我自己从来没有使用过它)。

从 Java8开始,一些本地变量名信息可以通过反射获得。

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

也许像 ASM这样的字节码工程库可以让您在运行时检查这些信息。我能想到的需要这些信息的唯一合理的地方是在开发工具中,因此字节码工程也可能用于其他目的。


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

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


}

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

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

看这个例子:

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

你可以这样做:

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

更新@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));
}
}
}