Odbicie Java: Jak uzyskać nazwę zmiennej?


139

Czy za pomocą Java Reflection można uzyskać nazwę zmiennej lokalnej? Na przykład, jeśli mam to:

Foo b = new Foo();
Foo a = new Foo();
Foo r = new Foo();

czy można zaimplementować metodę, która może znaleźć nazwy tych zmiennych, na przykład:

public void baz(Foo... foos)
{
    for (Foo foo: foos) {
        // Print the name of each foo - b, a, and r
        System.out.println(***); 
    }
}

EDYCJA: To pytanie różni się od Czy w Javie istnieje sposób na znalezienie nazwy zmiennej, która została przekazana do funkcji? w tym, że bardziej czysto stawia pytanie, czy można użyć refleksji do określenia nazwy zmiennej lokalnej, podczas gdy drugie pytanie (w tym przyjęta odpowiedź) jest bardziej ukierunkowane na testowanie wartości zmiennych.


11
Wszystkie świetne odpowiedzi! Dziękujemy wszystkim za odpowiedzi i komentarze - to była interesująca i wnikliwa dyskusja.
David Koelle,


To jest możliwe. Zobacz mój [sedno] [1]. Działa dla JDK 1.1 do JDK 7. [1]: gist.github.com/2011728
Wendal Chen


3
To nie jest duplikat i zaktualizowałem moje pytanie, aby wyjaśnić dlaczego. Jeśli już, to drugie pytanie jest duplikatem (lub szczególnym przypadkiem) tego!
David Koelle,

Odpowiedzi:


65

Począwszy od wersji Java 8, niektóre informacje o nazwach zmiennych lokalnych są dostępne poprzez odbicie. Zobacz sekcję „Aktualizacja” poniżej.

Pełne informacje są często przechowywane w plikach zajęć. Jedną z optymalizacji czasu kompilacji jest usunięcie go, oszczędzając miejsce (i zapewniając pewne zaciemnienie). Jednak gdy jest obecny, każda metoda ma atrybut tabeli zmiennych lokalnych, który zawiera typ i nazwę zmiennych lokalnych oraz zakres instrukcji, w których są one objęte.

Być może biblioteka inżynierii kodu bajtowego, taka jak ASM , pozwoliłaby na wgląd w te informacje w czasie wykonywania. Jedynym rozsądnym miejscem, w którym potrzebuję tych informacji, jest narzędzie programistyczne, więc inżynieria kodu bajtowego może być przydatna również do innych celów.


Aktualizacja: Ograniczona obsługa tego została dodana do języka Java 8. Nazwy parametrów (specjalna klasa zmiennych lokalnych) są teraz dostępne poprzez odbicie. Może to między innymi pomóc w zastąpieniu @ParameterNameadnotacji używanych przez kontenery iniekcji zależności.


49

W ogóle nie jest to możliwe. Nazwy zmiennych nie są przekazywane w Javie (i mogą zostać usunięte z powodu optymalizacji kompilatora).

EDYCJA (dotyczy komentarzy):

Jeśli wycofasz się z pomysłu używania go jako parametrów funkcji, oto alternatywa (której nie użyłbym - patrz poniżej):

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

Będą problemy, jeśli a == b, a == r, or b == ristnieją inne pola, które mają te same odniesienia.

EDYCJA jest teraz niepotrzebna, ponieważ pytanie zostało wyjaśnione



1
-1: Myślę, że źle zrozumiałeś. @David znajduje się za polami, a nie zmiennymi lokalnymi. Zmienne lokalne są rzeczywiście niedostępne za pośrednictwem interfejsu API odbicia.
Luke Woodward

Myślę, że Pourquoi Litytestdata ma rację. Oczywiście pól nie można zoptymalizować, więc Marcel J. musi myśleć o zmiennych lokalnych.
Michael Myers

3
@David: Musisz edytować, aby wyjaśnić, że chodziło Ci o pola, a nie zmienne lokalne. Oryginalne pytanie podaje kod, który deklaruje b, a i r jako zmienne lokalne.
Jason S

7
Miałem na myśli zmienne lokalne i zredagowałem pytanie, aby to odzwierciedlić. Pomyślałem, że uzyskanie nazw zmiennych może nie być możliwe, ale pomyślałem, że zapytam TAK, zanim uznam, że jest to niemożliwe.
David Koelle

30

( Edycja: usunięto dwie poprzednie odpowiedzi, jedną za udzielenie odpowiedzi na pytanie przed edycją, a drugą za bycie, jeśli nie absolutnie błędne, to przynajmniej blisko tego. )

Jeśli kompilujesz z informacjami debugowania w ( javac -g), nazwy zmiennych lokalnych są przechowywane w pliku .class. Weźmy na przykład tę prostą klasę:

class TestLocalVarNames {
    public String aMethod(int arg) {
        String local1 = "a string";
        StringBuilder local2 = new StringBuilder();
        return local2.append(local1).append(arg).toString();
    }
}

Po skompilowaniu z javac -g:vars TestLocalVarNames.java, nazwy zmiennych lokalnych znajdują się teraz w pliku .class. javap„s -lflag («Broszura numer linii i lokalnych tabel zmienna») może je pokazać.

javap -l -c TestLocalVarNames przedstawia:

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

Specyfikacja maszyny wirtualnej wyjaśnia, co tu widzimy:

§4.7.9 LocalVariableTableAtrybut :

LocalVariableTableCechą jest opcjonalna cecha o zmiennej długości Code(§4.7.3) atrybutu. Może być używany przez debuggery do określenia wartości danej zmiennej lokalnej podczas wykonywania metody.

W LocalVariableTablesklepach nazwy i typy zmiennych w każdej szczelinie, więc możliwe jest, aby dopasować je do kodu bajtowego z. W ten sposób debugery mogą wykonywać „Obliczanie wyrażenia”.

Jednak, jak powiedział Erickson, nie ma sposobu, aby uzyskać dostęp do tego stołu poprzez normalną refleksję. Jeśli nadal jesteś zdeterminowany, aby to zrobić, uważam, że architektura debugera platformy Java (JPDA) pomoże (ale nigdy jej nie używałem).


1
Ups, erickson opublikował, kiedy redagowałem, a teraz mu zaprzeczam. Co prawdopodobnie oznacza, że ​​się mylę.
Michael Myers

Domyślnie javacumieszcza lokalną tabelę zmiennych w klasie dla każdej metody, aby ułatwić debugowanie. Użyj -lopcji, aby javapwyświetlić lokalną tabelę zmiennych.
erickson

Wygląda na to, że nie domyślnie. Musiałem javac -g:varsgo zdobyć. (Próbowałem edytować tę odpowiedź przez ostatnie trzy godziny, ale jak powiedziałem, moje połączenie sieciowe ma problemy, co utrudnia badanie.)
Michael Myers

2
Masz rację, przepraszam za to. To numery linii są domyślnie włączone.
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()można użyć w przypadku, gdy chcesz również nazwy prywatnych pól
coffeMug

10

Możesz zrobić tak:

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

Twoja odpowiedź działa dobrze, aby uzyskać wszystkie fieads. Aby uzyskać tylko jedno pole, gdy używam: YourClass.class.getDeclaredField ("field1"); Dostaję NullPointer. Jaki jest problem w używaniu go? Jak używać metody getDeclaredField?
Shashi Ranjan,

0

Wszystko, co musisz zrobić, to utworzyć tablicę pól, a następnie ustawić ją na żądaną klasę, jak pokazano poniżej.

Field fld[] = (class name).class.getDeclaredFields();   
for(Field x : fld)
{System.out.println(x);}

Na przykład, jeśli tak

Field fld[] = Integer.class.getDeclaredFields();
          for(Field x : fld)
          {System.out.println(x);}

dostaniesz

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

zaktualizuj odpowiedź @Marcel Jackwerth dla generała.

i działa tylko z atrybutem klasy, nie działa ze zmienną metody.

    /**
     * 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

zobacz ten przykład:

PersonneTest pt=new PersonneTest();
System.out.println(pt.getClass().getDeclaredFields().length);
Field[]x=pt.getClass().getDeclaredFields();
System.out.println(x[1].getName());
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.