¿Cómo recorrer los atributos de una clase en Java?

85

¿Cómo puedo recorrer los atributos de una clase en java dinámicamente?

Por ejemplo:

public class MyClass{
    private type1 att1;
    private type2 att2;
    ...

    public void function(){
        for(var in MyClass.Attributes){
            System.out.println(var.class);
        }
    }
}

¿Es esto posible en Java?

Zakaria
fuente

Respuestas:

100

No hay soporte lingüístico para hacer lo que está pidiendo.

Puede acceder de forma reflexiva a los miembros de un tipo en tiempo de ejecución utilizando la reflexión (por ejemplo, con Class.getDeclaredFields()para obtener una matriz de Field), pero dependiendo de lo que esté intentando hacer, esta puede no ser la mejor solución.

Ver también

Preguntas relacionadas


Ejemplo

Aquí hay un ejemplo simple para mostrar solo algo de lo que la reflexión es capaz de hacer.

import java.lang.reflect.*;

public class DumpFields {
    public static void main(String[] args) {
        inspect(String.class);
    }
    static <T> void inspect(Class<T> klazz) {
        Field[] fields = klazz.getDeclaredFields();
        System.out.printf("%d fields:%n", fields.length);
        for (Field field : fields) {
            System.out.printf("%s %s %s%n",
                Modifier.toString(field.getModifiers()),
                field.getType().getSimpleName(),
                field.getName()
            );
        }
    }
}

El fragmento anterior utiliza la reflexión para inspeccionar todos los campos declarados de class String; produce la siguiente salida:

7 fields:
private final char[] value
private final int offset
private final int count
private int hash
private static final long serialVersionUID
private static final ObjectStreamField[] serialPersistentFields
public static final Comparator CASE_INSENSITIVE_ORDER

Effective Java 2nd Edition, Item 53: Preferir interfaces a la reflexión

Estos son extractos del libro:

Dado un Classobjeto, puede obtener Constructor, Methody las Fieldinstancias que representan los constructores, métodos y campos de la clase. [Ellos] le permiten manipular reflexivamente a sus contrapartes subyacentes . Este poder, sin embargo, tiene un precio:

  • Pierde todos los beneficios de la verificación en tiempo de compilación.
  • El código requerido para realizar el acceso reflexivo es torpe y detallado.
  • El rendimiento sufre.

Como regla general, no se debe acceder a los objetos de forma reflexiva en aplicaciones normales en tiempo de ejecución.

Hay algunas aplicaciones sofisticadas que requieren reflexión. Los ejemplos incluyen [... omitido a propósito ...] Si tiene alguna duda sobre si su aplicación cae en una de estas categorías, probablemente no lo haga.

poligenelubricantes
fuente
de hecho, según el valor de los campos, ¿quiero escribir o no escribir el valor de cada campo?
Zakaria
@Zakaria: sí, puedes lograr esto con la reflexión, pero dependiendo de qué es lo que intentas hacer, hay diseños mucho mejores.
poligenelubricantes
De hecho, tengo un informe para generar a partir de una clase, y dependiendo del valor de los campos de clase que quiero poner o no en esos campos, ¿cuál es el mejor diseño que puedo implementar?
Zakaria
1
@Zakaria: adelante, prueba a reflexionar. Hay Field.getque puede utilizar para leer los valores de un campo de forma reflexiva. Si es así private, es posible que pueda solucionarlo setAccessible. Sí, la reflexión es muy poderosa y le permite hacer cosas como inspeccionar privatecampos, sobrescribir finalcampos, invocar privateconstructores, etc. Si necesita más ayuda con el aspecto del diseño, probablemente debería escribir una nueva pregunta con mucha más información. Quizás no necesite estos muchos atributos en primer lugar (por ejemplo, use enumo List, etc.).
poligenelubricantes
1
Los genéricos no tienen ningún uso aquí. solo hazlostatic void inspect(Class<?> klazz)
user102008
45

Acceder a los campos directamente no es realmente un buen estilo en Java. Sugeriría crear métodos getter y setter para los campos de su bean y luego usar las clases Introspector y BeanInfo del paquete java.beans.

MyBean bean = new MyBean();
BeanInfo beanInfo = Introspector.getBeanInfo(MyBean.class);
for (PropertyDescriptor propertyDesc : beanInfo.getPropertyDescriptors()) {
    String propertyName = propertyDesc.getName();
    Object value = propertyDesc.getReadMethod().invoke(bean);
}
Jörn Horstmann
fuente
¿Hay alguna forma de obtener solo las propiedades del bean, no la clase? es decir, evitar MyBean.class devuelto por getPropertyDescriptors
hbprotoss
@hbprotoss, si lo entiendo correctamente, puede verificar propertyDesc.getReadMethod().getDeclaringClass() != Object.classo puede especificar una clase para detener el análisis como el segundo parámetro como getBeanInfo(MyBean.class, Object.class).
Jörn Horstmann
20

Si bien estoy de acuerdo con la respuesta de Jörn si su clase se ajusta a la especificación JavaBeabs, aquí hay una buena alternativa si no lo hace y usa Spring.

Spring tiene una clase llamada ReflectionUtils que ofrece una funcionalidad muy poderosa, incluyendo doWithFields (clase, devolución de llamada) , un método de estilo visitante que le permite iterar sobre los campos de una clase usando un objeto de devolución de llamada como este:

public void analyze(Object obj){
    ReflectionUtils.doWithFields(obj.getClass(), field -> {

        System.out.println("Field name: " + field.getName());
        field.setAccessible(true);
        System.out.println("Field value: "+ field.get(obj));

    });
}

Pero aquí hay una advertencia: la clase está etiquetada como "solo para uso interno", lo cual es una pena si me preguntas

Sean Patrick Floyd
fuente
13

Una forma sencilla de iterar sobre los campos de clase y obtener valores del objeto:

 Class<?> c = obj.getClass();
 Field[] fields = c.getDeclaredFields();
 Map<String, Object> temp = new HashMap<String, Object>();

 for( Field field : fields ){
      try {
           temp.put(field.getName().toString(), field.get(obj));
      } catch (IllegalArgumentException e1) {
      } catch (IllegalAccessException e1) {
      }
 }
Rickey
fuente
Quizás una clase o cualquier objeto.
Rickey
@Rickey ¿Hay alguna forma de establecer un nuevo valor para los atributos haciendo un bucle en cada campo?
hyperfkcb
@hyperfkcb Es posible que obtenga una excepción de modificación al intentar cambiar los valores de las propiedades / campos sobre los que está iterando.
Rickey
6

Java tiene Reflection (java.reflection. *), Pero sugeriría buscar en una biblioteca como Apache Beanutils, hará que el proceso sea mucho menos complicado que usar la reflexión directamente.

Tassos Bassoukos
fuente
0

Aquí hay una solución que ordena las propiedades alfabéticamente y las imprime todas junto con sus valores:

public void logProperties() throws IllegalArgumentException, IllegalAccessException {
  Class<?> aClass = this.getClass();
  Field[] declaredFields = aClass.getDeclaredFields();
  Map<String, String> logEntries = new HashMap<>();

  for (Field field : declaredFields) {
    field.setAccessible(true);

    Object[] arguments = new Object[]{
      field.getName(),
      field.getType().getSimpleName(),
      String.valueOf(field.get(this))
    };

    String template = "- Property: {0} (Type: {1}, Value: {2})";
    String logMessage = System.getProperty("line.separator")
            + MessageFormat.format(template, arguments);

    logEntries.put(field.getName(), logMessage);
  }

  SortedSet<String> sortedLog = new TreeSet<>(logEntries.keySet());

  StringBuilder sb = new StringBuilder("Class properties:");

  Iterator<String> it = sortedLog.iterator();
  while (it.hasNext()) {
    String key = it.next();
    sb.append(logEntries.get(key));
  }

  System.out.println(sb.toString());
}
Benny Neugebauer
fuente