Réflexion Java: Comment obtenir le nom d'une variable?

139

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 qui peut 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 Y a-t-il un moyen en Java de trouver le nom de la variable qui a été passée à une fonction? en ce qu 'elle pose plus purement la question de savoir si l'on peut utiliser la réflexion pour déterminer le nom d'une variable locale, alors que l'autre question (y compris la réponse acceptée) est plus centrée sur le test des valeurs de variables.

David Koelle
la source
11
Toutes les bonnes réponses! Merci à tous d'avoir répondu et commenté - ce fut une discussion intéressante et perspicace.
David Koelle
J'ai trouvé ce tutoriel détaillé pour la réflexion
Dhiral Pandya
C'est possible. Voir mon [essentiel] [1]. Fonctionne pour JDK 1.1 à JDK 7. [1]: gist.github.com/2011728
Wendal Chen
duplication possible de Y a
user253751
3
Pas un double, et mis à jour ma question pour expliquer pourquoi. Si quoi que ce soit, cette autre question est un doublon (ou un cas particulier) de celle-ci!
David Koelle

Réponses:

65

À partir de Java 8, certaines informations de nom de variable locale sont disponibles par réflexion. Consultez 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 fournissant une certaine obsfuscation). Cependant, lorsqu'elle est présente, chaque méthode a 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 sont dans la portée.

Peut-être une bibliothèque d'ingénierie de code d'octet comme 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 ces informations est dans un outil de développement, et donc l'ingénierie de byte-code est susceptible d'être utile à d'autres fins également.


Mise à jour: une prise en charge limitée a été ajoutée à Java 8. Les noms de paramètres (une classe spéciale de variables locales) sont désormais disponibles via la réflexion. Entre autres objectifs, cela peut aider à remplacer les @ParameterNameannotations utilisées par les conteneurs d'injection de dépendances.

Erickson
la source
49

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

EDIT (lié aux commentaires):

Si vous vous éloignez 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 il y a d'autres champs qui ont les mêmes références.

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

Marcel Jackwerth
la source
Alors, comment expliquez-vous cela: java.sun.com/javase/6/docs/api/java/lang/reflect/Field.html ?
Programmeur hors
1
-1: Je pense que vous avez mal compris. @David est après les champs, pas les variables locales. Les variables locales ne sont en effet pas disponibles via l'API Reflection.
Luke Woodward
Je pense que Pourquoi Litytestdata a raison. Il est évident que les champs ne peuvent pas être optimisés, donc Marcel J. doit penser aux variables locales.
Michael Myers
3
@David: Vous devez modifier pour clarifier que vous vouliez dire des champs plutôt que des variables locales. La question d'origine donne un code qui déclare b, a et r comme variables locales.
Jason S
7
Je voulais dire des variables locales, et j'ai modifié la question pour refléter cela. Je pensais qu'il ne serait peut-être pas possible d'obtenir des noms de variables, mais j'ai pensé que je demanderais SO avant de considérer cela comme impossible.
David Koelle
30

( Modifier: deux réponses précédentes supprimées, une pour répondre à la question telle qu'elle se présentait avant les modifications et une pour être, si ce n'est absolument faux, du moins proche. )

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, prenez 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 la compilation avec javac -g:vars TestLocalVarNames.java, les noms des variables locales sont maintenant dans le fichier .class. javapde-l indicateur de ("Imprimer le numéro de ligne et les tables de variables locales") peut les afficher.

javap -l -c TestLocalVarNames spectacles:

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

La spécification VM explique ce que nous voyons ici:

§4.7.9 L' LocalVariableTableattribut :

le LocalVariableTable attribut est un attribut de longueur variable facultatif 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.

le LocalVariableTable stocke les noms et les types des variables dans chaque emplacement, il est donc possible de les faire correspondre avec le bytecode. C'est ainsi que les débogueurs peuvent faire "Evaluate expression".

Comme l'a dit Erickson, cependant, 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 l' architecture du débogueur de plate-forme Java (JPDA) vous aidera (mais je ne l'ai jamais utilisée moi-même).

Michael Myers
la source
1
Oh-oh, erickson a posté pendant que je montais et maintenant je le contredit. Ce qui signifie probablement que je me trompe.
Michael Myers
Par défaut, javacplace une table de variables locales dans la classe pour chaque méthode pour faciliter le débogage. Utilisez l' -loption pour javapafficher la table des variables locales.
erickson
Pas par défaut, semble-t-il. J'ai dû l'utiliser javac -g:varspour l'obtenir. (J'essaie de modifier cette réponse depuis trois heures, mais comme je l'ai dit, ma connexion réseau rencontre des problèmes, ce qui rend la recherche difficile.)
Michael Myers
2
Vous avez raison, désolé pour ça. Ce sont les numéros de ligne qui sont "activés" par défaut.
erickson le
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
la source
3
getDeclaredFields()peut être utilisé au cas où vous voudriez également des noms de champs privés
coffeMug
10

Vous pouvez faire comme ceci:

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
la source
Votre réponse fonctionne bien pour obtenir tous les fieads. Pour obtenir un seul champ, lorsque j'utilise: YourClass.class.getDeclaredField ("field1"); J'obtiens NullPointer. Quel est le problème de son utilisation? Comment utiliser la méthode getDeclaredField?
Shashi Ranjan
0

Tout ce que vous avez à faire est de créer un tableau de champs, puis de le définir dans 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);}

tu aurais

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
la source
0

mettre à jour la réponse de @Marcel Jackwerth pour le général.

et ne travaillant qu'avec l'attribut de classe, ne fonctionnant pas avec la variable de méthode.

    /**
     * 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
la source
-1

voir cet exemple:

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