Java Reflection: Wie erhalte ich den Namen einer Variablen?

139

Ist es mit Java Reflection möglich, den Namen einer lokalen Variablen abzurufen? Zum Beispiel, wenn ich das habe:

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

ist es möglich, eine Methode zu implementieren, die die Namen dieser Variablen finden kann, wie folgt:

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

BEARBEITEN: Diese Frage unterscheidet sich von Gibt es in Java eine Möglichkeit, den Namen der Variablen zu finden, die an eine Funktion übergeben wurde? , dass die Frage, ob man den Namen einer lokalen Variablen mithilfe von Reflexion bestimmen kann, reiner gestellt wird, während sich die andere Frage (einschließlich der akzeptierten Antwort) mehr auf das Testen von Variablenwerten konzentriert.

David Koelle
quelle
11
Alles gute Antworten! Vielen Dank an alle für die Beantwortung und Kommentierung - dies war eine interessante und aufschlussreiche Diskussion.
David Koelle
Ich fand dieses Detail-Tutorial für Reflexion
Dhiral Pandya
Es ist möglich. Siehe mein [Kern] [1]. Funktioniert für JDK 1.1 bis JDK 7. [1]: gist.github.com/2011728
Wendal Chen
3
Kein Duplikat, und meine Frage aktualisiert, um zu erklären, warum. Wenn überhaupt, ist diese andere Frage ein Duplikat (oder ein Sonderfall) dieser Frage!
David Koelle

Antworten:

65

Ab Java 8 sind einige Informationen zu lokalen Variablennamen durch Reflektion verfügbar. Siehe den Abschnitt "Update" weiter unten.

Vollständige Informationen werden häufig in Klassendateien gespeichert. Eine Optimierung zur Kompilierungszeit besteht darin, sie zu entfernen, Platz zu sparen (und eine gewisse Verschleierung bereitzustellen). Wenn es jedoch vorhanden ist, verfügt jede Methode über ein Tabellenattribut für lokale Variablen, das den Typ und den Namen lokaler Variablen sowie den Befehlsbereich auflistet, in dem sie sich im Gültigkeitsbereich befinden.

Möglicherweise können Sie mit einer Bytecode-Engineering-Bibliothek wie ASM diese Informationen zur Laufzeit überprüfen. Der einzige vernünftige Ort, an den ich denken kann, um diese Informationen zu benötigen, ist ein Entwicklungstool. Daher ist das Bytecode-Engineering wahrscheinlich auch für andere Zwecke nützlich.


Update: Eingeschränkte Unterstützung für diese wurde hinzugefügt , um Java 8. Parameter (eine spezielle Klasse von lokalen Variablen) Namen sind nun über Reflexion zur Verfügung. Dies kann unter anderem dazu beitragen, @ParameterNameAnmerkungen zu ersetzen, die von Abhängigkeitsinjektionscontainern verwendet werden.

erickson
quelle
49

Es ist überhaupt nicht möglich. Variablennamen werden in Java nicht kommuniziert (und können aufgrund von Compiler-Optimierungen auch entfernt werden).

BEARBEITEN (im Zusammenhang mit Kommentaren):

Wenn Sie von der Idee zurücktreten, es als Funktionsparameter verwenden zu müssen, finden Sie hier eine Alternative (die ich nicht verwenden würde - siehe unten):

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

Es wird Probleme geben, wenn a == b, a == r, or b == roder es andere Felder gibt, die dieselben Referenzen haben.

BEARBEITEN jetzt unnötig, da die Frage geklärt wurde

Marcel Jackwerth
quelle
Wie erklären Sie das dann: java.sun.com/javase/6/docs/api/java/lang/reflect/Field.html ?
Outlaw Programmer
1
-1: Ich denke du hast es falsch verstanden. @David steht hinter Feldern, nicht nach lokalen Variablen. Lokale Variablen sind in der Tat über die Reflection-API nicht verfügbar.
Luke Woodward
Ich denke, Pourquoi Litytestdata ist richtig. Offensichtlich können Felder nicht entfernt werden, daher muss Marcel J. an lokale Variablen denken.
Michael Myers
3
@ David: Sie müssen bearbeiten, um zu verdeutlichen, dass Sie eher Felder als lokale Variablen gemeint haben. Die ursprüngliche Frage enthält Code, der b, a und r als lokale Variablen deklariert.
Jason S
7
Ich meinte lokale Variablen und habe die Frage bearbeitet, um dies widerzuspiegeln. Ich dachte, es wäre möglicherweise nicht möglich, Variablennamen zu erhalten, aber ich dachte, ich würde SO fragen, bevor ich es für unmöglich halte.
David Koelle
30

( Bearbeiten: Zwei vorherige Antworten wurden entfernt, eine für die Beantwortung der Frage, wie sie vor der Bearbeitung stand, und eine, um, wenn nicht absolut falsch, zumindest nahe daran zu sein. )

Wenn Sie mit Debug-Informationen zu ( javac -g) kompilieren , werden die Namen lokaler Variablen in der .class-Datei gespeichert. Nehmen Sie zum Beispiel diese einfache Klasse:

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

Nach dem Kompilieren mit befinden javac -g:vars TestLocalVarNames.javasich die Namen der lokalen Variablen jetzt in der .class-Datei. javapDas -lFlag ("Zeilennummer und lokale Variablentabellen drucken") kann sie anzeigen.

javap -l -c TestLocalVarNames zeigt an:

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

Die VM-Spezifikation erklärt, was wir hier sehen:

§4.7.9 Das LocalVariableTableAttribut :

Das LocalVariableTableAttribut ist ein optionales Attribut variabler Länge eines Code(§4.7.3) Attributs. Es kann von Debuggern verwendet werden, um den Wert einer bestimmten lokalen Variablen während der Ausführung einer Methode zu bestimmen.

Das LocalVariableTablespeichert die Namen und Typen der Variablen in jedem Slot, so dass es möglich ist, sie mit dem Bytecode abzugleichen. Auf diese Weise können Debugger "Ausdruck auswerten" ausführen.

Wie erickson sagte, gibt es jedoch keine Möglichkeit, durch normale Reflexion auf diese Tabelle zuzugreifen. Wenn Sie immer noch entschlossen sind, dies zu tun, wird die Java Platform Debugger Architecture (JPDA) meiner Meinung nach helfen (aber ich habe sie selbst nie verwendet).

Michael Myers
quelle
1
Oh, oh, erickson hat geschrieben, während ich redigierte, und jetzt widerspreche ich ihm. Was wahrscheinlich bedeutet, dass ich falsch liege.
Michael Myers
Standardmäßig wird javacfür jede Methode eine lokale Variablentabelle in die Klasse eingefügt, um das Debuggen zu unterstützen. Verwenden Sie die -lOption, javapum die lokale Variablentabelle anzuzeigen.
Erickson
Es scheint nicht standardmäßig. Ich musste es benutzen javac -g:vars, um es zu bekommen. (Ich habe in den letzten drei Stunden versucht, diese Antwort zu bearbeiten, aber wie gesagt, meine Netzwerkverbindung hat Probleme, was die Recherche schwierig macht.)
Michael Myers
2
Du hast recht, tut mir leid. Es sind Zeilennummern, die standardmäßig aktiviert sind.
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
quelle
3
getDeclaredFields()kann verwendet werden, wenn Sie auch die Namen von privaten Feldern möchten
coffeMug
10

Sie können dies tun:

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
quelle
Ihre Antwort funktioniert gut, um alle Fragen zu beantworten. Um nur ein Feld zu erhalten, wenn ich Folgendes verwende: YourClass.class.getDeclaredField ("field1"); Ich bekomme NullPointer. Was ist das Problem bei der Verwendung? Wie soll ich die getDeclaredField-Methode verwenden?
Shashi Ranjan
0

Alles, was Sie tun müssen, ist, ein Array von Feldern zu erstellen und es dann auf die gewünschte Klasse zu setzen, wie unten gezeigt.

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

Zum Beispiel, wenn Sie es getan haben

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

du würdest bekommen

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
quelle
0

Aktualisieren Sie die Antwort von @Marcel Jackwerth für allgemein.

und nur mit Klassenattributen arbeiten, nicht mit Methodenvariablen.

    /**
     * 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));
            }
        }
    }
Colin Wang
quelle
-1

siehe dieses Beispiel:

PersonneTest pt=new PersonneTest();
System.out.println(pt.getClass().getDeclaredFields().length);
Field[]x=pt.getClass().getDeclaredFields();
System.out.println(x[1].getName());
h.meknassi
quelle