web-dev-qa-db-fra.com

Java Reflection: Comment obtenir le nom d'une variable?

En utilisant Java Reflection, est-il possible d’obtenir le nom d’une variable locale? Par exemple, si j'ai ceci:

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

est-il possible d'implémenter une méthode permettant de trouver les noms de ces variables, comme ceci:

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

EDIT: Cette question est différente de Existe-t-il un moyen dans Java de trouver le nom de la variable qui a été transmise à une fonction? dans qu'il pose plus purement la question de savoir si l'on peut utiliser la réflexion pour déterminer le nom d'une variable locale, tandis que l'autre question (y compris la réponse acceptée) est davantage axée sur le test des valeurs de variables.

128
David Koelle

À partir de Java 8, certaines informations de nom de variable locale sont disponibles par réflexion. Voir la section "Mise à jour" ci-dessous.

Des informations complètes sont souvent stockées dans des fichiers de classe. Une optimisation au moment de la compilation consiste à le supprimer, en économisant de l’espace (et en offrant une certaine obsfuscation). Cependant, chaque fois qu'elle est présente, chaque méthode possède un attribut de table de variables locales qui répertorie le type et le nom des variables locales, ainsi que la plage d'instructions où elles se trouvent dans la portée.

Peut-être qu'une bibliothèque d'ingénierie à code octet telle que ASM vous permettrait d'inspecter ces informations au moment de l'exécution. Le seul endroit raisonnable auquel je puisse penser pour avoir besoin de cette information est dans un outil de développement. L'ingénierie en octets est donc probablement utile à d'autres fins.


Mise à jour: La prise en charge limitée de ce mode était ajouté à Java 8. Paramètre (une classe spéciale de locaux nom de variable) sont maintenant disponibles par réflexion. Ceci peut notamment permettre de remplacer les annotations @ParameterName utilisées par les conteneurs d'injection de dépendance.

62
erickson

Ce n'est pas du tout possible. Les noms de variable ne sont pas communiqués entre Java (et peuvent également être supprimés en raison des optimisations du compilateur).

EDIT (lié aux commentaires):

Si vous vous écartez de l'idée de devoir l'utiliser comme paramètres de fonction, voici une alternative (que je n'utiliserais pas - voir ci-dessous):

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

Il y aura des problèmes si a == b, a == r, or b == r ou s'il existe d'autres champs qui portent les mêmes références.

EDIT maintenant inutile puisque la question a été clarifiée

48
Marcel Jackwerth

( Edit: deux réponses précédentes ont été supprimées, l'une pour répondre à la question telle qu'elle était avant les modifications et l'autre pour être, si ce n'est absolument pas faux, du moins proche de celle-ci. )

Si vous compilez avec les informations de débogage sur (javac -g), les noms des variables locales sont conservés dans le fichier .class. Par exemple, prenons cette classe simple:

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

Après avoir compilé avec javac -g:vars TestLocalVarNames.Java, les noms des variables locales sont maintenant dans le fichier .class. L 'indicateur -l de javap ("Imprimer le numéro de ligne et les tables de variables locales") peut les afficher.

javap -l -c TestLocalVarNames montre:

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 VM spec explique ce que nous voyons ici:

§4.7.9 L'attribut LocalVariableTable :

L'attribut LocalVariableTable est un attribut optionnel de longueur variable d'un attribut Code (§4.7.3). Il peut être utilisé par les débogueurs pour déterminer la valeur d'une variable locale donnée lors de l'exécution d'une méthode.

La LocalVariableTable stocke les noms et les types des variables dans chaque emplacement, de sorte qu'il est possible de les faire correspondre au bytecode. C'est comment les débogueurs peuvent faire "Evaluer une expression".

Comme l'a dit Erickson, il n'y a aucun moyen d'accéder à cette table par une réflexion normale. Si vous êtes toujours déterminé à le faire, je pense que le Architecture de débogueur de plate-forme Java (JPDA) aidera (mais je ne l'ai jamais utilisé moi-même).

29
Michael Myers
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));
  }
 }

}
14
Peeter

Vous pouvez faire comme ça:

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

Tout ce que vous avez à faire est de créer un tableau de champs, puis de le définir sur la classe souhaitée, comme indiqué ci-dessous.

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

Par exemple si vous avez fait

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

vous obtiendrez

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
error_null_pointer