Java Reflection: Como obter o nome de uma variável?

Usando o Java Reflection, é possível obter o nome de uma variável local? Por exemplo, se eu tiver isso:

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

é possível implementar um método que pode encontrar os nomes dessas variables, assim:

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

EDIT: Esta questão é diferente de Existe uma maneira em Java para encontrar o nome da variável que foi passada para uma function? na medida em que mais puramente faz a pergunta sobre se alguém pode usar a reflection para determinar o nome de uma variável local, enquanto a outra questão (incluindo a resposta aceita) está mais focada em testar valores de variables.

A partir do Java 8, algumas informações sobre o nome da variável local estão disponíveis através da reflection. Veja a seção “Atualização” abaixo.

Informações completas são frequentemente armazenadas em arquivos de class. Uma otimização em tempo de compilation é removê-lo, economizando espaço (e fornecendo alguma atenção). No entanto, quando está presente, cada método tem um atributo de tabela de variável local que lista o tipo e o nome de variables ​​locais e o intervalo de instruções em que estão no escopo.

Talvez uma biblioteca de engenharia de código de bytes como o ASM permita que você inspecione essas informações em tempo de execução. O único lugar razoável em que posso pensar para precisar dessas informações é em uma ferramenta de desenvolvimento, e, portanto, a engenharia de códigos de bytes provavelmente também será útil para outras finalidades.


Atualização: O suporte limitado para isso foi incluído no Java 8. Os nomes de parâmetro (uma class especial de variável local) agora estão disponíveis por meio de reflection. Entre outras finalidades, isso pode ajudar a replace as annotations @ParameterName usadas pelos contêineres de injeção de dependência.

Não é possível a todos. Os nomes de variables ​​não são comunicados em Java (e também podem ser removidos devido a otimizações do compilador).

EDITAR (relacionado aos comentários):

Se você se afastar da idéia de ter que usá-lo como parâmetros de function, aqui está uma alternativa (que eu não usaria – veja abaixo):

 public void printFieldNames(Object obj, Foo... foos) { List fooList = Arrays.asList(foos); for(Field field : obj.getClass().getFields()) { if(fooList.contains(field.get()) { System.out.println(field.getName()); } } } 

Haverá problemas se a == b, a == r, or b == r ou se houver outros campos que tenham as mesmas referências.

EDITAR agora desnecessário desde que a pergunta foi esclarecida

( Edit: duas respostas anteriores removidas, uma para responder a pergunta como estava antes das edições e outra para ser, se não absolutamente errado, pelo menos perto dela. )

Se você compilar com informações de debugging em ( javac -g ), os nomes das variables ​​locais serão mantidos no arquivo .class. Por exemplo, pegue esta class simples:

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

Depois de compilar com javac -g:vars TestLocalVarNames.java , os nomes das variables ​​locais estão agora no arquivo .class. javap -l flag (“Imprimir número da linha e tabelas de variables ​​locais”) pode mostrá-los.

javap -l -c TestLocalVarNames mostra:

 class TestLocalVarNames extends java.lang.Object{ TestLocalVarNames(); Code: 0: aload_0 1: invokespecial #1; //Method java/lang/Object."":()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."":()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; } 

A especificação da VM explica o que estamos vendo aqui:

§4.7.9 O atributo LocalVariableTable :

O atributo LocalVariableTable é um atributo opcional de comprimento variável de um atributo Code (§4.7.3). Ele pode ser usado por depuradores para determinar o valor de uma determinada variável local durante a execução de um método.

O LocalVariableTable armazena os nomes e tipos das variables ​​em cada slot, então é possível combiná-los com o bytecode. É assim que os depuradores podem fazer “Avaliar expressão”.

Como erickson disse, no entanto, não há como acessar essa tabela através da reflection normal. Se você ainda está determinado a fazer isso, acredito que a Arquitetura do Java Platform Debugger (JPDA) ajudará (mas eu nunca usei isso sozinho).

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

Você pode fazer assim:

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

Tudo o que você precisa fazer é criar uma matriz de campos e, em seguida, configurá-lo para a turma desejada, como mostrado abaixo.

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

Por exemplo, se você fez

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

você pegaria

 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 

veja este exemplo:

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