Відбиття Java: Як отримати ім'я змінної?


139

Чи можна за допомогою відображення 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(***); 
    }
}

EDIT: Це питання відрізняється від Чи існує спосіб у Java знайти ім'я змінної, переданої функції? тим, що більш чисто задається питання про те, чи можна використовувати відображення для визначення імені локальної змінної, тоді як інше питання (включаючи прийняту відповідь) більш орієнтоване на тестування значень змінних.


11
Всі чудові відповіді! Дякую всім за відповіді та коментарі - це було цікавим та глибоким обговоренням.
Девід Коелле


Можливо. Дивіться мою [суть] [1]. Працює для JDK 1.1 до JDK 7. [1]: gist.github.com/2011728
Wendal Chen


3
Не дублікат, і оновив моє запитання, щоб пояснити, чому. Якщо що-небудь, це інше питання - це дублікат (або особливий випадок) цього!
Девід Коелл

Відповіді:


65

Що стосується Java 8, деяка інформація про локальну змінну імен доступна через відображення. Дивіться розділ "Оновлення" нижче.

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

Можливо, бібліотека інженерних байт-кодів, як ASM , дозволить вам перевірити цю інформацію під час виконання. Єдине розумне місце, яке я можу подумати про необхідність цієї інформації, - це інструмент розробки, тому інженерія байт-кодів, ймовірно, буде корисна і для інших цілей.


Оновлення: обмежена підтримка цього була додана до Java 8. Імена параметрів (спеціальний клас локальної змінної) тепер доступні через відображення. Серед інших цілей це може допомогти замінити @ParameterNameпримітки, які використовуються контейнерами для ін'єкцій залежностей.


49

Це взагалі неможливо. Імена змінних не повідомляються в межах Java (вони також можуть бути видалені через оптимізацію компілятора).

EDIT (пов’язано з коментарями):

Якщо ви відступили від ідеї необхідності використовувати його як параметри функції, ось альтернатива (яку я б не використовував - див. Нижче):

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
-1: Я думаю, ви неправильно зрозуміли. @David - це поля, а не локальні змінні. Локальні змінні дійсно недоступні через Reflection API.
Люк Вудвард

Я думаю, що Pourquoi Litytestdata має рацію. Очевидно, що поля неможливо оптимізувати, тому Марсель Дж. Повинен думати про локальні змінні.
Майкл Майерс

3
@David: Вам потрібно відредагувати, щоб уточнити, що ви мали на увазі поля, а не локальні змінні. Оригінальне запитання дає код, який оголошує b, a і r як локальні змінні.
Jason S

7
Я мав на увазі локальні змінні, і я редагував питання, щоб це відобразити. Я думав, що неможливо отримати імена змінних, але я подумав, що попрошу ТА, перш ніж вважати це неможливим.
Девід Коелле

30

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

Якщо ви компілюєте інформацію про налагодження на ( 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 Platform (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?
Shashi Ranjan

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.