Java思考:如何获取变量的名称?


139

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

编辑:这个问题不同于Java中是否有办法找到传递给函数的变量的名称?因为它更纯粹地问一个问题,即一个人是否可以使用反射来确定局部变量的名称,而另一个问题(包括公认的答案)则更侧重于测试变量的值。


11
所有好的答案!感谢大家的答复和评论-这是一个有趣而有见地的讨论。
David Koelle


有可能的。见我的要点[1]。适用于JDK 1.1到JDK7
Wendal Chen


3
不是重复的,更新了我的问题以解释原因。如果有的话,那另一个问题就是这个问题的重复(或特殊情况)!
David Koelle

Answers:


65

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

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

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


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


49

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

由于问题得到澄清,现在无需编辑



1
-1:我认为您误会了。@David在字段之后,而不是局部变量之后。确实不能通过反射API使用局部变量。
路加·伍德沃德

我认为Pourquoi Litytestdata是正确的。显然,无法优化字段,因此Marcel J.必须考虑局部变量。
迈克尔·迈尔斯

3
@David:您需要进行编辑以阐明您的意思是字段而不是局部变量。原始问题给出了将b,a和r声明为局部变量的代码。
杰森S

7
我的意思是局部变量,并且我对问题进行了编辑以反映这一点。我以为可能无法获得变量名,但我认为在考虑不可能之前先问一下SO。
David Koelle

30

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

如果使用(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;
}

虚拟机规范解释了我们现在看到的:

§4.7.9 LocalVariableTable属性

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

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

但是,正如埃里克森所说,无法通过正常反射访问该表。如果您仍然决心这样做,那么我相信Java平台调试器体系结构(JPDA)会有所帮助(但我自己从未使用过)。


1
呃,埃里克森在我编辑时发贴,现在我在和他矛盾。这可能意味着我错了。
迈克尔·迈尔斯

默认情况下,javac将局部变量表放入每个方法的类中以帮助调试。使用该-l选项javap查看局部变量表。
erickson

似乎不是默认情况。我不得不用javac -g:vars它。(在过去的三个小时中,我一直在尝试编辑此答案,但是正如我所说的,我的网络连接出现问题,这使我们难以研究。)
Michael Myers

2
您是对的,对此感到抱歉。默认情况下,行号是“开”的。
erickson

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

}

3
getDeclaredFields()可以在需要私有字段名称的情况下使用
coffeMug ​​2015年

10

您可以这样:

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());
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.