Отражение Java: как получить имя переменной?

141

Можно ли получить имя локальной переменной с помощью 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
Всем отличных ответов! Спасибо всем за ответы и комментарии - это была интересная и содержательная дискуссия.
Дэвид Келле,
Я нашел этот
подробный
Это возможно. См. Мою [суть] [1]. Работает с JDK 1.1 - JDK 7. [1]: gist.github.com/2011728
Wendal Chen,
3
Не дубликат, а обновленный мой вопрос, чтобы объяснить, почему. Во всяком случае, этот другой вопрос является дубликатом (или частным случаем) этого!
Дэвид Келле

Ответы:

66

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

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

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


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

Эриксон
источник
50

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

ИЗМЕНИТЬ теперь ненужно, так как вопрос прояснился

Марсель Джекверт
источник
Тогда как вы это объясните: java.sun.com/javase/6/docs/api/java/lang/reflect/Field.html ?
Outlaw Programmer
1
-1: Я думаю, вы неправильно поняли. @David находится после полей, а не локальных переменных. Локальные переменные действительно недоступны через Reflection API.
Люк Вудворд,
Я считаю, что Pourquoi Litytestdata верна. Очевидно, что поля нельзя оптимизировать, поэтому Марсель Дж. Должен думать о локальных переменных.
Майкл Майерс
3
@David: Вам нужно отредактировать, чтобы уточнить, что вы имели в виду поля, а не локальные переменные. Исходный вопрос дает код, объявляющий b, a и r как локальные переменные.
Джейсон С.
8
Я имел в виду локальные переменные и отредактировал вопрос, чтобы отразить это. Я думал, что получить имена переменных может быть невозможно, но решил, что спрошу ТАК прежде, чем сочту это невозможным.
Дэвид Келле,
31

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

Если вы компилируете с отладочной информацией на ( 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«S -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чтобы получить это. (Я пытался отредактировать этот ответ в течение последних трех часов, но, как я уже сказал, у моего сетевого подключения возникают проблемы, что затрудняет исследование.)
Майкл Майерс
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));
  }
 }

}
Peeter
источник
3
getDeclaredFields()можно использовать, если вам нужны имена частных полей
coffeMug
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());   
}
tinker_fairy
источник
Ваш ответ отлично работает, чтобы получить все злодеяния. Чтобы получить только одно поле, когда я использую: YourClass.class.getDeclaredField ("field1"); Я получаю NullPointer. В чем проблема с его использованием? Как мне использовать метод getDeclaredField?
Шаши Ранджан
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
error_null_pointer
источник
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());
h.meknassi
источник