Java Reflection: come ottenere il nome di una variabile?


139

Utilizzando Java Reflection, è possibile ottenere il nome di una variabile locale? Ad esempio, se ho questo:

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

è possibile implementare un metodo che può trovare i nomi di quelle variabili, in questo modo:

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

EDIT: Questa domanda è diversa da C'è un modo in Java per trovare il nome della variabile che è stata passata a una funzione? in quanto pone più puramente la domanda se si può usare la riflessione per determinare il nome di una variabile locale, mentre l'altra domanda (compresa la risposta accettata) è più focalizzata sul test dei valori delle variabili.


11
Tutte ottime risposte! Grazie a tutti per aver risposto e commentato: questa è stata una discussione interessante e approfondita.
David Koelle,


È possibile. Vedi il mio [gist] [1]. Funziona da JDK 1.1 a JDK 7. [1]: gist.github.com/2011728
Wendal Chen,


3
Non è un duplicato e ho aggiornato la mia domanda per spiegare il perché. Semmai, quell'altra domanda è un duplicato (o un caso speciale) di questo!
David Koelle,

Risposte:


65

A partire da Java 8, alcune informazioni sul nome della variabile locale sono disponibili tramite reflection. Vedi "Aggiornamento" sezione seguito.

Le informazioni complete sono spesso archiviate in file di classe. Un'ottimizzazione in fase di compilazione è rimuoverla, risparmiando spazio (e fornendo un po 'di offuscamento). Tuttavia, quando è presente, ogni metodo ha un attributo di tabella delle variabili locali che elenca il tipo e il nome delle variabili locali e l'intervallo di istruzioni in cui si trovano nell'ambito.

Forse una libreria di ingegneria a codice byte come ASM ti permetterebbe di ispezionare queste informazioni in fase di esecuzione. L'unico posto ragionevole a cui riesco a pensare per aver bisogno di queste informazioni è in uno strumento di sviluppo, e quindi l'ingegneria del codice byte è probabilmente utile anche per altri scopi.


Aggiornamento: il supporto limitato per questo è stato aggiunto a Java 8. I nomi dei parametri (una classe speciale di variabile locale) sono ora disponibili tramite reflection. Tra gli altri scopi, ciò può aiutare a sostituire le @ParameterNameannotazioni utilizzate dai contenitori per l'iniezione delle dipendenze.


49

E ' non è possibile a tutti. I nomi delle variabili non vengono comunicati all'interno di Java (e potrebbero anche essere rimossi a causa delle ottimizzazioni del compilatore).

EDIT (relativo ai commenti):

Se ti allontani dall'idea di doverlo usare come parametri di funzione, ecco un'alternativa (che non vorrei usare - vedi sotto):

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

Ci saranno problemi se a == b, a == r, or b == ro ci sono altri campi che hanno gli stessi riferimenti.

EDIT ora non necessario poiché la domanda è stata chiarita



1
-1: Penso che tu abbia frainteso. @David è dopo i campi, non variabili locali. Le variabili locali non sono effettivamente disponibili tramite l'API Reflection.
Luke Woodward,

Penso che Pourquoi Litytestdata abbia ragione. Ovviamente i campi non possono essere ottimizzati, quindi Marcel J. deve pensare alle variabili locali.
Michael Myers

3
@David: è necessario modificare per chiarire che intendevi campi anziché variabili locali. La domanda originale fornisce il codice che dichiara b, a e r come variabili locali.
Jason S,

7
Intendevo variabili locali e ho modificato la domanda per riflettere ciò. Ho pensato che potrebbe non essere possibile ottenere nomi di variabili, ma ho pensato di chiedere SO prima di considerarlo impossibile.
David Koelle,

30

( Modifica: due risposte precedenti rimosse, una per rispondere alla domanda prima delle modifiche e una per essere, se non assolutamente sbagliata, almeno vicino ad essa. )

Se si compila con le informazioni di debug su ( javac -g), i nomi delle variabili locali vengono conservati nel file .class. Ad esempio, prendi questa semplice classe:

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

Dopo la compilazione con javac -g:vars TestLocalVarNames.java, i nomi delle variabili locali sono ora nel file .class. javapil -lflag ("Stampa il numero di riga e le tabelle delle variabili locali") può mostrarli.

javap -l -c TestLocalVarNames Spettacoli:

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

Le specifiche della VM spiegano cosa stiamo vedendo qui:

§4.7.9 L' LocalVariableTableattributo :

L' LocalVariableTableattributo è un attributo opzionale a lunghezza variabile di un attributo Code(§4.7.3). Può essere utilizzato dai debugger per determinare il valore di una determinata variabile locale durante l'esecuzione di un metodo.

I LocalVariableTablenegozi i nomi ei tipi delle variabili in ogni slot, quindi è possibile abbinare il tutto con il bytecode. Ecco come i debugger possono fare "Valuta espressione".

Come ha detto Erickson, tuttavia, non c'è modo di accedere a questa tabella attraverso la normale riflessione. Se sei ancora determinato a farlo, credo che la Java Platform Debugger Architecture (JPDA) ti aiuterà (ma non l'ho mai usato da solo).


1
Uh-oh, erickson ha pubblicato mentre stavo montando e ora lo contraddico. Il che probabilmente significa che mi sbaglio.
Michael Myers

Per impostazione predefinita, javacinserisce una tabella delle variabili locali nella classe per ciascun metodo per facilitare il debug. Utilizzare l' -lopzione per javapvisualizzare la tabella delle variabili locali.
Erickson,

Non di default, a quanto pare. Ho dovuto usare javac -g:varsper ottenerlo. (Ho cercato di modificare questa risposta nelle ultime tre ore, ma come ho già detto la mia connessione di rete sta avendo problemi, il che rende difficile la ricerca.)
Michael Myers

2
Hai ragione, mi dispiace per quello. Sono i numeri di riga che sono "attivi" per impostazione predefinita.
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()può essere utilizzato nel caso in cui desideri anche i nomi dei campi privati
coffeMug

10

Puoi fare così:

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

La tua risposta funziona bene per ottenere tutti i nemici. Per ottenere un solo campo, quando uso: YourClass.class.getDeclaredField ("field1"); Ottengo NullPointer. Qual è il problema nell'usarlo? Come dovrei usare il metodo getDeclaredField?
Shashi Ranjan l'

0

Tutto quello che devi fare è creare una matrice di campi e quindi impostarlo sulla classe che desideri come mostrato di seguito.

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

Ad esempio se lo hai fatto

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

otterresti

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

aggiorna la risposta di @Marcel Jackwerth per il generale.

e funziona solo con l'attributo class, non funziona con la variabile metodo.

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

vedi questo esempio:

PersonneTest pt=new PersonneTest();
System.out.println(pt.getClass().getDeclaredFields().length);
Field[]x=pt.getClass().getDeclaredFields();
System.out.println(x[1].getName());
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.