【问题标题】:Java Reflection: How to get the name of a variable?Java反射:如何获取变量的名称?
【发布时间】:2010-10-19 03:46:00
【问题描述】:

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

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

【问题讨论】:

标签: java reflection


【解决方案1】:

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

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

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


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

【讨论】:

    【解决方案2】:

    这完全不可能。变量名称在 Java 中不通信(也可能由于编译器优化而被删除)。

    编辑(与 cmets 相关):

    如果您放弃将其用作函数参数的想法,这里有一个替代方案(我不会使用 - 见下文):

    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:我想你误会了。 @David 在字段之后,而不是局部变量。局部变量确实不能通过反射 API 获得。
    • 我认为 Pourquoi Litytestdata 是对的。显然字段不能被优化掉,所以 Marcel J. 必须考虑局部变量。
    • @David:您需要进行编辑以澄清您的意思是字段而不是局部变量。原始问题给出了将 b、a 和 r 声明为局部变量的代码。
    • 我的意思是局部变量,我已经编辑了问题以反映这一点。我认为可能无法获得变量名,但我想在认为不可能之前我会问一下。
    【解决方案3】:

    编辑:删除了两个先前的答案,一个用于回答编辑前的问题,一个用于即使不是绝对错误,至少接近它。

    如果您使用 (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 spec 解释了我们在这里看到的内容:

    §4.7.9 The LocalVariableTable Attribute

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

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

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

    【讨论】:

    • 呃,哦,埃里克森在我编辑时发帖,现在我在反驳他。这可能意味着我错了。
    • javac默认为每个方法在类中放置一个局部变量表来辅助调试。使用-l 选项到javap 查看局部变量表。
    • 默认情况下似乎不是。我必须使用javac -g:vars 才能得到它。 (过去三个小时我一直在尝试编辑这个答案,但正如我所说,我的网络连接有问题,这使得研究变得困难。)
    • 你是对的,对此感到抱歉。默认情况下,它是“开启”的行号。
    【解决方案4】:
    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));
      }
     }
    
    }
    

    【讨论】:

    • getDeclaredFields() 可以在您想要私有字段名称的情况下使用
    【解决方案5】:

    你可以这样做:

    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");我得到空指针。使用它有什么问题?我应该如何使用 getDeclaredField 方法?
    【解决方案6】:

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

    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
    

    【讨论】:

      【解决方案7】:

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

      【讨论】:

        【解决方案8】:

        看这个例子:

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

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2020-05-12
          • 1970-01-01
          • 1970-01-01
          • 2012-09-01
          • 2011-02-03
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多