Отражение 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));
  }
 }

}

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

Ваш ответ отлично работает, чтобы получить все злодеяния. Чтобы получить только одно поле, когда я использую: 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

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());
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.