Отражение Java: Как получить имя переменной? - PullRequest
126 голосов
/ 13 апреля 2009

Используя 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 способ найти имя переменной, которая была передана в функцию? в том, что он более чисто задает вопрос о том, можно использовать отражение, чтобы определить имя локальной переменной, тогда как другой вопрос (включая принятый ответ) больше ориентирован на тестирование значений переменных.

Ответы [ 7 ]

62 голосов
/ 13 апреля 2009

Начиная с Java 8, некоторая информация об имени локальной переменной доступна через рефлексию. См. «Обновление» раздел ниже.

Полная информация часто хранится в файлах классов. Одна оптимизация во время компиляции состоит в том, чтобы удалить это, экономя место (и предоставляя некоторую запутанность). Однако, когда он присутствует, у каждого метода есть атрибут таблицы локальных переменных, в котором перечислены тип и имя локальных переменных, а также диапазон инструкций, в которых они находятся.

Возможно, библиотека конструирования байт-кода, такая как ASM , позволит вам проверить эту информацию во время выполнения. Единственное разумное место, где я могу подумать, что мне нужна эта информация, - это инструмент разработки, и поэтому разработка байт-кода, вероятно, будет полезна и для других целей.


Обновление: Ограниченная поддержка для этого была добавлена ​​в Java 8. Имена параметров (специальный класс локальных переменных) теперь доступны через отражение. Помимо прочего, это может помочь заменить аннотации @ParameterName, используемые контейнерами для внедрения зависимостей.

47 голосов
/ 13 апреля 2009

Это не возможно вообще. Имена переменных не передаются в 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 или есть другие поля с такими же ссылками.

РЕДАКТИРОВАТЬ теперь не нужно, поскольку вопрос прояснился

29 голосов
/ 13 апреля 2009

( Редактировать: два предыдущих ответа удалены: один для ответа на вопрос в том виде, в каком он стоял до правок, и другой за то, что он был, если не абсолютно неправ, по крайней мере близок к нему. )

Если вы компилируете с отладочной информацией о (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 хранит имена и типы переменных в каждом слоте, поэтому их можно сопоставить с байт-кодом. Вот как отладчики могут выполнять «Оценивать выражение».

Однако, как сказал Эриксон, нет никакого способа получить доступ к этой таблице через обычное отражение. Если вы все еще намерены это сделать, я считаю, что Архитектура отладчика платформы Java (JPDA) поможет (но я никогда не использовал ее сам).

14 голосов
/ 30 декабря 2010
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));
  }
 }

}
8 голосов
/ 11 декабря 2012

Вы можете сделать так:

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());   
}
0 голосов
/ 26 апреля 2012

см. Этот пример:

PersonneTest pt=new PersonneTest();
System.out.println(pt.getClass().getDeclaredFields().length);
Field[]x=pt.getClass().getDeclaredFields();
System.out.println(x[1].getName());
0 голосов
/ 20 марта 2012

Все, что вам нужно сделать, это создать массив полей, а затем установить его в класс, который вы хотите, как показано ниже.

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
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...