Отражение Java: Как получить имя переменной?
Можно ли с помощью 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 ответов
Начиная с Java 8, некоторая информация об имени локальной переменной доступна через отражение. Смотрите раздел "Обновление" ниже.
Полная информация часто хранится в файлах классов. Одна оптимизация во время компиляции состоит в том, чтобы удалить это, экономя место (и предоставляя некоторую запутанность). Однако, когда он присутствует, у каждого метода есть атрибут таблицы локальных переменных, в котором перечислены тип и имя локальных переменных, а также диапазон инструкций, в которых они находятся.
Возможно, библиотека конструирования байт-кода, такая как ASM, позволит вам проверить эту информацию во время выполнения. Единственное разумное место, где я могу найти эту информацию, - это инструмент разработки, и поэтому разработка байт-кода, вероятно, будет полезна и для других целей.
Обновление: Ограниченная поддержка для этого была добавлена в Java 8. Имена параметров (специальный класс локальных переменных) теперь доступны через отражение. Среди прочих целей это может помочь заменить @ParameterName
аннотации, используемые контейнерами для внедрения зависимостей.
Это невозможно вообще. Имена переменных не передаются в 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
или есть другие поля, которые имеют те же ссылки.
РЕДАКТИРОВАТЬ теперь не нужно, поскольку вопрос прояснился
(Изменить: два предыдущих ответа удалены: один для ответа на вопрос в том виде, в каком он стоял до правок, и один за то, что он был, если не абсолютно неправ, хотя бы близок к нему.)
Если вы компилируете с отладочной информацией о (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;
}
Спецификация VM объясняет, что мы видим здесь:
§4.7.9 LocalVariableTable
Атрибут:
LocalVariableTable
Атрибут является необязательным атрибутом переменной длиныCode
(§4.7.3) атрибут. Он может использоваться отладчиками для определения значения заданной локальной переменной во время выполнения метода.
LocalVariableTable
хранит имена и типы переменных в каждом слоте, поэтому их можно сопоставить с байт-кодом. Вот как отладчики могут выполнять "Оценивать выражение".
Однако, как сказал Эриксон, нет способа получить доступ к этой таблице с помощью обычного отражения. Если вы все еще намерены это сделать, я считаю, что архитектура отладчика платформы Java (JPDA) поможет (но я никогда не использовал ее сам).
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));
}
}
}
Вы можете сделать так:
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());
}
Обновить ответ @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));
}
}
}
Все, что вам нужно сделать, это создать массив полей, а затем установить его в класс, который вы хотите, как показано ниже.
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
Посмотрите этот пример:
PersonneTest pt=new PersonneTest();
System.out.println(pt.getClass().getDeclaredFields().length);
Field[]x=pt.getClass().getDeclaredFields();
System.out.println(x[1].getName());