Reflexión de Java: ¿Cómo obtener el nombre de una variable?

139

Usando Java Reflection, ¿es posible obtener el nombre de una variable local? Por ejemplo, si tengo esto:

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

¿Es posible implementar un método que pueda encontrar los nombres de esas variables?

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

EDITAR: Esta pregunta es diferente de ¿Hay alguna forma en Java de encontrar el nombre de la variable que se pasó a una función? en el sentido de que hace una pregunta más pura sobre si uno puede usar la reflexión para determinar el nombre de una variable local, mientras que la otra pregunta (incluida la respuesta aceptada) se centra más en probar los valores de las variables.

David Koelle
fuente
11
¡Todas excelentes respuestas! Gracias a todos por responder y comentar: esta ha sido una discusión interesante y perspicaz.
David Koelle el
Encontré este Tutorial detallado para Reflexión
Dhiral Pandya
Es posible. Ver mi [esencia] [1]. Funciona para JDK 1.1 a JDK 7. [1]: gist.github.com/2011728
Wendal Chen
3
No es un duplicado, y actualicé mi pregunta para explicar por qué. ¡En todo caso, esa otra pregunta es un duplicado (o caso especial) de esta!
David Koelle

Respuestas:

65

A partir de Java 8, parte de la información del nombre de la variable local está disponible a través de la reflexión. Consulte la sección "Actualización" a continuación.

La información completa a menudo se almacena en archivos de clase. Una optimización en tiempo de compilación es eliminarlo, ahorrando espacio (y proporcionando algo de ofuscación). Sin embargo, cuando está presente, cada método tiene un atributo de tabla de variables locales que enumera el tipo y el nombre de las variables locales, y el rango de instrucciones donde están dentro del alcance.

Quizás una biblioteca de ingeniería de código de bytes como ASM le permitiría inspeccionar esta información en tiempo de ejecución. El único lugar razonable en el que puedo pensar para necesitar esta información es en una herramienta de desarrollo, por lo que es probable que la ingeniería de código de bytes también sea útil para otros fines.


Actualización: se agregó soporte limitado para esto a Java 8. Los nombres de parámetros (una clase especial de variable local) ahora están disponibles a través de la reflexión. Entre otros propósitos, esto puede ayudar a reemplazar las @ParameterNameanotaciones utilizadas por los contenedores de inyección de dependencia.

erickson
fuente
49

Es que no es posible en absoluto. Los nombres de las variables no se comunican dentro de Java (y también pueden eliminarse debido a las optimizaciones del compilador).

EDITAR (relacionado con comentarios):

Si abandonas la idea de tener que usarlo como parámetros de función, aquí hay una alternativa (que no usaría, ver más abajo):

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

Habrá problemas si a == b, a == r, or b == rhay otros campos que tengan las mismas referencias.

EDITAR ahora innecesario ya que la pregunta se aclaró

Marcel Jackwerth
fuente
Entonces, ¿cómo explicas esto: java.sun.com/javase/6/docs/api/java/lang/reflect/Field.html ?
Outlaw Programmer el
1
-1: Creo que has entendido mal. @David busca campos, no variables locales. Las variables locales no están disponibles a través de la API de Reflection.
Luke Woodward el
Creo que Pourquoi Litytestdata tiene razón. Obviamente, los campos no se pueden optimizar, por lo que Marcel J. debe estar pensando en variables locales.
Michael Myers
3
@David: necesita editar para aclarar que se refería a campos en lugar de variables locales. La pregunta original proporciona un código que declara b, a y r como variables locales.
Jason S
77
Me refería a las variables locales, y he editado la pregunta para reflejar eso. Pensé que podría no ser posible obtener nombres de variables, pero pensé en preguntar SO antes de considerarlo imposible.
David Koelle el
30

( Editar: se eliminaron dos respuestas anteriores, una por responder la pregunta tal como estaba antes de las ediciones y otra por estar, si no está absolutamente equivocada, al menos cerca de ella. )

Si compila con información de depuración en ( javac -g), los nombres de las variables locales se mantienen en el archivo .class. Por ejemplo, tome esta clase simple:

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

Después de compilar con javac -g:vars TestLocalVarNames.java, los nombres de las variables locales ahora están en el archivo .class. javapEl -lindicador ("Imprimir número de línea y tablas de variables locales") puede mostrarlas.

javap -l -c TestLocalVarNames muestra:

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 especificación de VM explica lo que estamos viendo aquí:

§4.7.9 El LocalVariableTableatributo :

El LocalVariableTableatributo es un atributo opcional de longitud variable de un atributo Code(§4.7.3). Los depuradores pueden usarlo para determinar el valor de una variable local dada durante la ejecución de un método.

Las LocalVariableTabletiendas de los nombres y tipos de las variables en cada ranura, por lo que es posible igualar para arriba con el código de bytes. Así es como los depuradores pueden hacer "Evaluar expresión".

Sin embargo, como dijo Erickson, no hay forma de acceder a esta tabla a través de la reflexión normal. Si todavía está decidido a hacer esto, creo que la arquitectura Java Platform Debugger Architecture (JPDA) ayudará (pero nunca la he usado yo mismo).

Michael Myers
fuente
1
Oh, oh, Erickson publicó mientras estaba editando y ahora lo estoy contradiciendo. Lo que probablemente significa que estoy equivocado.
Michael Myers
De forma predeterminada, javaccoloca una tabla de variables locales en la clase para cada método para ayudar a la depuración. Use la -lopción para javapver la tabla de variables locales.
erickson el
No por defecto, parece. Tuve que usar javac -g:varspara conseguirlo. (He estado tratando de editar esta respuesta durante las últimas tres horas, pero como dije, mi conexión de red tiene problemas, lo que dificulta la investigación)
Michael Myers
2
Tienes razón, perdón por eso. Son los números de línea que están "activados" de forma predeterminada.
erickson el
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
fuente
3
getDeclaredFields()puede usarse en caso de que también desee los nombres de los campos privados
coffeMug
10

Puedes hacer así:

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
fuente
Tu respuesta funciona bien para atrapar a todos los demonios. Para obtener solo un campo, cuando uso: YourClass.class.getDeclaredField ("field1"); Obtengo NullPointer. ¿Cuál es el problema al usarlo? ¿Cómo debo usar el método getDeclaredField?
Shashi Ranjan
0

Todo lo que necesita hacer es hacer una matriz de campos y luego configurarlo en la clase que desee como se muestra a continuación.

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

Por ejemplo si lo hiciste

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

obtendrías

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

actualizar la respuesta de @Marcel Jackwerth para general.

y solo funciona con el atributo de clase, no funciona con la variable 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
fuente
-1

mira este ejemplo:

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