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

139

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 variáveis, assim:

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

Edição: Esta pergunta é diferente de Existe uma maneira em Java para encontrar o nome da variável que foi passada para uma função? na medida em que faz mais puramente a pergunta sobre se alguém pode usar a reflexão para determinar o nome de uma variável local, enquanto a outra pergunta (incluindo a resposta aceita) está mais focada no teste de valores de variáveis.

David Koelle
fonte
11
Todas ótimas respostas! Obrigado a todos por responder e comentar - esta tem sido uma discussão interessante e esclarecedora.
31515 David Koelle
Eu encontrei esta Detalhe Tutorial de Reflexão
Dhiral Pandya
É possível. Veja minha [essência] [1]. Funciona para o JDK 1.1 ao JDK 7. [1]: gist.github.com/2011728
Wendal Chen
3
Não é uma duplicata, e atualizei minha pergunta para explicar o porquê. Se alguma coisa, essa outra pergunta é uma duplicata (ou caso especial) desta!
precisa

Respostas:

65

No Java 8, algumas informações sobre o nome da variável local estão disponíveis através da reflexão. Veja a seção "Atualização" abaixo.

Informações completas geralmente são armazenadas em arquivos de classe. Uma otimização em tempo de compilação é removê-lo, economizando espaço (e oferecendo alguma ofuscação). No entanto, quando está presente, cada método possui um atributo de tabela de variável local que lista o tipo e o nome das variáveis ​​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 inspecionar essas informações em tempo de execução. O único lugar razoável em que posso pensar em precisar dessas informações é em uma ferramenta de desenvolvimento e, portanto, a engenharia de código de bytes provavelmente também será útil para outros fins.


Atualização: Suporte limitado a isso foi adicionado ao Java 8. Os nomes de parâmetros (uma classe especial de variável local) agora estão disponíveis via reflexão. Entre outras finalidades, isso pode ajudar a substituir as @ParameterNameanotações usadas pelos contêineres de injeção de dependência.

erickson
fonte
49

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

EDIT (relacionado aos comentários):

Se você se afastar da ideia de usá-lo como parâmetros de função, aqui está uma alternativa (que eu não usaria - veja abaixo):

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

Haverá problemas se a == b, a == r, or b == rou houver outros campos com as mesmas referências.

EDIT agora desnecessário, pois a pergunta foi esclarecida

Marcel Jackwerth
fonte
Então, como você explica isso: java.sun.com/javase/6/docs/api/java/lang/reflect/Field.html ?
Outlaw Programmer
1
-1: Eu acho que você não entendeu. @ David é após os campos, não variáveis ​​locais. As variáveis ​​locais estão realmente indisponíveis através da API do Reflection.
22415 Luke Woodward
Eu acho que Pourquoi Litytestdata está certo. Obviamente, os campos não podem ser otimizados, então Marcel J. deve estar pensando em variáveis ​​locais.
Michael Myers
3
@ David: Você precisa editar para esclarecer que você quis dizer campos em vez de variáveis ​​locais. A pergunta original fornece um código que declara b, a er como variáveis ​​locais.
23909 Jason S
7
Eu quis dizer variáveis ​​locais e editei a pergunta para refletir isso. Eu pensei que talvez não fosse possível obter nomes de variáveis, mas imaginei perguntar ao SO antes de considerá-lo impossível.
31909 David Koelle
30

( Editar: duas respostas anteriores removidas, uma por responder à pergunta como estava antes das edições e outra por estar, se não absolutamente errada, pelo menos próxima a ela. )

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

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

Após compilar com javac -g:vars TestLocalVarNames.java, os nomes das variáveis ​​locais agora estão no arquivo .class. javapO -lsinalizador ("Número da linha de impressão e tabelas de variáveis ​​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."<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;
}

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

§4.7.9 O LocalVariableTableatributo :

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

Ele LocalVariableTablearmazena os nomes e tipos das variáveis ​​em cada slot, para que seja possível combiná-los com o bytecode. É assim que os depuradores podem fazer "Avaliar expressão".

Como disse Erickson, no entanto, não há como acessar esta tabela através da reflexão normal. Se você ainda estiver determinado a fazer isso, acredito que a JPDA (Java Platform Debugger Architecture) ajudará (mas nunca a usei).

Michael Myers
fonte
1
Erickson postou enquanto eu estava editando e agora estou contradizendo ele. O que provavelmente significa que estou errado.
Michael Myers
Por padrão, javaccoloca uma tabela de variável local na classe para cada método para ajudar na depuração. Use a -lopção para javapver a tabela de variáveis ​​locais.
22620
Não por padrão, parece. Eu tive que usar javac -g:varspara obtê-lo. (Eu venho tentando editar esta resposta para os últimos três horas, mas como eu disse a minha conexão de rede está com problemas, o que torna difícil para a pesquisa.)
Michael Myers
2
Você está certo, desculpe por isso. São os números de linha que estão "ativados" por padrão.
21410
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
fonte
3
getDeclaredFields()pode ser usado no caso de você deseja que os nomes de campo privada, bem
coffemug
10

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());   
}
tinker_fairy
fonte
Sua resposta funciona bem para obter todos os resultados. Para obter apenas um campo, quando eu uso: YourClass.class.getDeclaredField ("field1"); Eu recebo o NullPointer. Qual é o problema em usá-lo? Como devo usar o método getDeclaredField?
Shashi Ranjan
0

Tudo o que você precisa fazer é criar uma matriz de campos e depois configurá-la para a classe que você deseja, 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ê receberia

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
fonte
0

atualize a resposta geral de @Marcel Jackwerth.

e trabalhando apenas com o atributo class, não trabalhando com a variável de método

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

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());
h.meknassi
fonte