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.


11
Todas ótimas respostas! Obrigado a todos por responder e comentar - esta tem sido uma discussão interessante e esclarecedora.
— 31515 David Koelle


É 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.


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



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).


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

}

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

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

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

-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());
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.