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.
À 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.
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
( 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 attributCode
(§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).
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));
}
}
}
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());
}
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