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

166

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


11
所有的回答都很棒!感谢大家的回复和评论——这是一场有趣而深入的讨论。 - David Koelle
我找到了这个反射详细教程 - Dhiral Pandya
可以的。请查看我的[gist][1]。适用于JDK 1.1到JDK 7。[1]:https://gist.github.com/2011728 - Wendal Chen
3
不是重复问题,并且我已更新我的问题以解释原因。如果有什么的话,那个其他问题是这个问题的重复(或特殊情况)! - David Koelle
8个回答

71
自Java 8以来,一些局部变量名称信息可以通过反射获得。请参见下面的“更新”部分。
完整的信息通常存储在类文件中。编译时优化之一是删除它,以节省空间(并提供一些混淆)。但是,如果存在它,则每个方法都有一个本地变量表属性,列出本地变量的类型和名称,以及它们所处范围的指令范围。
也许像ASM这样的字节码工程库会允许您在运行时检查此信息。我能想到需要这些信息的唯一合理场所是开发工具中,因此字节码工程很可能对其他目的也有用。

更新: 这个功能在Java 8中有限制的支持。参数(一种特殊的本地变量类)名称现在可以通过反射获得。除其他用途外,这可以帮助替换依赖注入容器使用的@ParameterName注释。


57

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

编辑现在不再需要,因为问题已经澄清


那么你如何解释这个:http://java.sun.com/javase/6/docs/api/java/lang/reflect/Field.html? - Tim Frey
1
-1: 我认为你误解了。@David 是在寻找字段,而不是局部变量。局部变量确实无法通过反射 API 获得。 - Luke Woodward
3
@David:您需要进行编辑以澄清您的意思是指字段而不是局部变量。原始问题给出了将b、a和r声明为局部变量的代码。 - Jason S
请不要因为原帖作者的意思和问题不符而惩罚他们,因为没有人能读懂别人的心思。:( - Jason S
9
我确实是指本地变量,并且我已经编辑了问题以反映这一点。 我认为可能无法获取变量名,但在考虑不可能之前,我想问问SO。 - David Koelle
显示剩余4条评论

33

(编辑:删除了两个之前的回答,一个是回答修改前的问题,另一个虽然不完全错误,但至少接近错误。)

如果你使用调试信息进行编译(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规范解释了我们在这里看到的内容:

§4.7.9 LocalVariableTable 属性

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

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

不过正如 erickson 所说,没有办法通过正常反射访问该表格。如果您仍然决心要这样做,我相信Java平台调试体系结构(JPDA)将有所帮助(但我自己从未使用过)。


1
哎呀,当我在编辑时,Erickson 发布了帖子,现在我和他的观点相矛盾。这很可能意味着我是错的。 - Michael Myers
默认情况下,javac 会为每个方法在类中放置一个本地变量表以帮助调试。使用 -l 选项来运行 javap 命令以查看本地变量表。 - erickson
似乎默认情况下不支持,我必须使用 javac -g:vars 才能得到它。 (过去三个小时我一直在尝试编辑这个答案,但正如我所说,我的网络连接有问题,这使得研究变得困难。) - Michael Myers
2
你是对的,抱歉。默认情况下是显示行号。 - erickson

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

}

6
getDeclaredFields() 方法可以用来获取私有字段的名称。 - Vombat

14
你可以像这样做:
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());   
}

你的答案可以很好地获取所有字段。但是,当我只想获取一个字段时,使用以下代码:YourClass.class.getDeclaredField("field1"); 我会得到NullPointer异常。这是什么问题?我应该如何正确使用getDeclaredField方法? - Shashi Ranjan

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

0

更新 @Marcel Jackwerth 的答案以适用于一般情况。

仅适用于类属性,不适用于方法变量。

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

-1

看这个例子:

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接