Recuperando los nombres / valores de atributos heredados usando Java Reflection

128

Tengo un objeto Java 'ChildObj' que se extiende desde 'ParentObj'. Ahora, si es posible recuperar todos los nombres y valores de atributos de ChildObj, incluidos los atributos heredados también, utilizando el mecanismo de reflexión de Java.

Class.getFields me da la matriz de atributos públicos, y Class.getDeclaredFields me da la matriz de todos los campos, pero ninguno de ellos incluye la lista de campos heredados.

¿Hay alguna forma de recuperar los atributos heredados también?

Veera
fuente

Respuestas:

173

No, debes escribirlo tú mismo. Es un método recursivo simple llamado Class.getSuperClass () :

public static List<Field> getAllFields(List<Field> fields, Class<?> type) {
    fields.addAll(Arrays.asList(type.getDeclaredFields()));

    if (type.getSuperclass() != null) {
        getAllFields(fields, type.getSuperclass());
    }

    return fields;
}

@Test
public void getLinkedListFields() {
    System.out.println(getAllFields(new LinkedList<Field>(), LinkedList.class));
}
dfa
fuente
2
si. Pensé en eso. pero quería verificar si hay alguna otra forma de hacerlo. Gracias. :)
Veera
77
Pasar un argumento mutable y devolverlo probablemente no sea un gran diseño. fields.addAll (type.getDeclaredFields ()); sería más convencional que un bucle for mejorado con add.
Tom Hawtin - tackline
Sentiría la necesidad de al menos compilarlo (¡en stackoverflow!), Y probablemente agregar un poco de Arrays.asList.
Tom Hawtin - tackline
Parece que su código recopila todos los campos, también los campos privados y estáticos que no se heredan.
Peter Verhas
90
    public static List<Field> getAllFields(Class<?> type) {
        List<Field> fields = new ArrayList<Field>();
        for (Class<?> c = type; c != null; c = c.getSuperclass()) {
            fields.addAll(Arrays.asList(c.getDeclaredFields()));
        }
        return fields;
    }
Esko Luontola
fuente
9
Esta es mi solución preferida, sin embargo, lo llamaría "getAllFields" porque también devuelve los campos de la clase dada.
Pino
3
Aunque me gusta mucho la recursividad (¡es divertido!), Prefiero la legibilidad de este método y los parámetros más intuitivos (no se requiere pasar una nueva colección), no más if (implícito en la cláusula for) y no iteración sobre los campos sí mismos.
Remi Morin
muestra que recursivo es innecesario y .. ¡Me gustan los códigos cortos! ¡Gracias! :)
Aquarius Power
En muchos años, siempre pienso que el valor inicial de for es solo un número entero, con la pregunta de @ Veera creo que solo recursivo puede resolverlo, @ Esko Luontola su comando es increíble.
Touya Akira
@Esko: Muchas gracias. ¡Salvó el día! ¡Es conciso y funciona perfectamente!
gaurav
37

Si, en cambio, desea confiar en una biblioteca para lograr esto, Apache Commons Lang versión 3.2+ proporciona FieldUtils.getAllFieldsList:

import java.lang.reflect.Field;
import java.util.AbstractCollection;
import java.util.AbstractList;
import java.util.AbstractSequentialList;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;

import org.apache.commons.lang3.reflect.FieldUtils;
import org.junit.Assert;
import org.junit.Test;

public class FieldUtilsTest {

    @Test
    public void testGetAllFieldsList() {

        // Get all fields in this class and all of its parents
        final List<Field> allFields = FieldUtils.getAllFieldsList(LinkedList.class);

        // Get the fields form each individual class in the type's hierarchy
        final List<Field> allFieldsClass = Arrays.asList(LinkedList.class.getFields());
        final List<Field> allFieldsParent = Arrays.asList(AbstractSequentialList.class.getFields());
        final List<Field> allFieldsParentsParent = Arrays.asList(AbstractList.class.getFields());
        final List<Field> allFieldsParentsParentsParent = Arrays.asList(AbstractCollection.class.getFields());

        // Test that `getAllFieldsList` did truly get all of the fields of the the class and all its parents 
        Assert.assertTrue(allFields.containsAll(allFieldsClass));
        Assert.assertTrue(allFields.containsAll(allFieldsParent));
        Assert.assertTrue(allFields.containsAll(allFieldsParentsParent));
        Assert.assertTrue(allFields.containsAll(allFieldsParentsParentsParent));
    }
}
Chris
fuente
66
¡Auge! Me encanta no reinventar la rueda. Saludos por esto.
Joshua Pinter
6

Necesitas llamar a:

Class.getSuperclass().getDeclaredFields()

Recurrir a la jerarquía de herencia según sea necesario.

Nick Holt
fuente
5

Use la biblioteca Reflections:

public Set<Field> getAllFields(Class<?> aClass) {
    return org.reflections.ReflectionUtils.getAllFields(aClass);
}
Lukasz Ochmanski
fuente
4

Las soluciones recursivas están bien, el único pequeño problema es que devuelven un superconjunto de miembros declarados y heredados. Tenga en cuenta que el método getDeclaredFields () devuelve también métodos privados. Entonces, dado que navega por toda la jerarquía de superclases, incluirá todos los campos privados declarados en las superclases, y esos no se heredarán.

Un filtro simple con Modifier.isPublic || El predicado Modifier.isProtected haría:

import static java.lang.reflect.Modifier.isPublic;
import static java.lang.reflect.Modifier.isProtected;

(...)

List<Field> inheritableFields = new ArrayList<Field>();
for (Field field : type.getDeclaredFields()) {
    if (isProtected(field.getModifiers()) || isPublic(field.getModifiers())) {
       inheritableFields.add(field);
    }
}
Marek Dec
fuente
2
private static void addDeclaredAndInheritedFields(Class<?> c, Collection<Field> fields) {
    fields.addAll(Arrays.asList(c.getDeclaredFields())); 
    Class<?> superClass = c.getSuperclass(); 
    if (superClass != null) { 
        addDeclaredAndInheritedFields(superClass, fields); 
    }       
}

Versión de trabajo de la solución "DidYouMeanThatTomHa ..." arriba

Theo Platt
fuente
2

Con la biblioteca Spring Util, puede usar para verificar si existe un atributo específico en la clase:

Field field = ReflectionUtils.findRequiredField(YOUR_CLASS.class, "ATTRIBUTE_NAME");

log.info(field2.getName());

Documento de la API:
https://docs.spring.io/spring-data/commons/docs/current/api/org/springframework/data/util/ReflectionUtils.html

o

 Field field2 = ReflectionUtils.findField(YOUR_CLASS.class, "ATTRIBUTE_NAME");

 log.info(field2.getName());

Documento de la API:
https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/util/ReflectionUtils.html

@salud

Marcelo Rebouças
fuente
1

Puedes probar:

   Class parentClass = getClass().getSuperclass();
   if (parentClass != null) {
      parentClass.getDeclaredFields();
   }
Manuel Selva
fuente
1

¿Más corto y con menos objeto instanciado? ^^

private static Field[] getAllFields(Class<?> type) {
    if (type.getSuperclass() != null) {
        return (Field[]) ArrayUtils.addAll(getAllFields(type.getSuperclass()), type.getDeclaredFields());
    }
    return type.getDeclaredFields();
}
Alexis LEGROS
fuente
HI @ Alexis LEGROS: ArrayUtils no puede encontrar el símbolo.
Touya Akira
1
Esta clase es de Apache Commons Lang.
Alexis LEGROS
Apache ya tiene una función FieldUtils.getAllFields para manejar esta solicitud de pregunta.
Touya Akira
1

getFields (): Obtiene todos los campos públicos en toda la jerarquía de clases y
getDeclaredFields (): Obtiene todos los campos, independientemente de sus modificadores, pero solo para la clase actual. Por lo tanto, debe obtener toda la jerarquía involucrada.
Hace poco vi este código de org.apache.commons.lang3.reflect.FieldUtils

public static List<Field> getAllFieldsList(final Class<?> cls) {
        Validate.isTrue(cls != null, "The class must not be null");
        final List<Field> allFields = new ArrayList<>();
        Class<?> currentClass = cls;
        while (currentClass != null) {
            final Field[] declaredFields = currentClass.getDeclaredFields();
            Collections.addAll(allFields, declaredFields);
            currentClass = currentClass.getSuperclass();
        }
        return allFields;
}
Un hombre
fuente
0
private static void addDeclaredAndInheritedFields(Class c, Collection<Field> fields) {
    fields.addAll(Arrays.asList(c.getDeclaredFields()));
    Class superClass = c.getSuperclass();
    if (superClass != null) {
        addDeclaredAndInheritedFields(superClass, fields);
    }
}
DidYouMeanThatTomHawtin
fuente
0

Esta es una nueva redacción de la respuesta aceptada por @ user1079877. Es posible que una versión que no modifica un parámetro de la función y también utiliza algunas características modernas de Java.

public <T> Field[] getFields(final Class<T> type, final Field... fields) {
    final Field[] items = Stream.of(type.getDeclaredFields(), fields).flatMap(Stream::of).toArray(Field[]::new);
    if (type.getSuperclass() == null) {
        return items;
    } else {
        return getFields(type.getSuperclass(), items);
    }
}

Esta implementación también hace que la invocación sea un poco más concisa:

var fields = getFields(MyType.class);
scrutari
fuente
0

Hay un par de peculiaridades que FieldUtils no aborda, específicamente campos sintéticos (por ejemplo, inyectados por JaCoCo) y también el hecho de que un tipo de curso tiene un campo para cada instancia, y si está atravesando un gráfico de objeto, obtiene todos los campos y luego obtener los campos de cada uno de ellos, etc., entonces entrarás en un bucle infinito cuando toques una enumeración. Una solución extendida (y para ser honesto, estoy seguro de que debe vivir en una biblioteca en algún lugar) sería:

/**
 * Return a list containing all declared fields and all inherited fields for the given input
 * (but avoiding any quirky enum fields and tool injected fields).
 */
public List<Field> getAllFields(Object input) {
    return getFieldsAndInheritedFields(new ArrayList<>(), input.getClass());
}

private List<Field> getFieldsAndInheritedFields(List<Field> fields, Class<?> inputType) {
    fields.addAll(getFilteredDeclaredFields(inputType));
    return inputType.getSuperclass() == null ? fields : getFieldsAndInheritedFields(fields, inputType.getSuperclass());

}

/**
 * Where the input is NOT an {@link Enum} type then get all declared fields except synthetic fields (ie instrumented
 * additional fields). Where the input IS an {@link Enum} type then also skip the fields that are all the
 * {@link Enum} instances as this would lead to an infinite loop if the user of this class is traversing
 * an object graph.
 */
private List<Field> getFilteredDeclaredFields(Class<?> inputType) {
    return Arrays.asList(inputType.getDeclaredFields()).stream()
                 .filter(field -> !isAnEnum(inputType) ||
                         (isAnEnum(inputType) && !isSameType(field, inputType)))
                 .filter(field -> !field.isSynthetic())
                 .collect(Collectors.toList());

}

private boolean isAnEnum(Class<?> type) {
    return Enum.class.isAssignableFrom(type);
}

private boolean isSameType(Field input, Class<?> ownerType) {
    return input.getType().equals(ownerType);
}

Clase de prueba en Spock (y Groovy agrega campos sintéticos):

class ReflectionUtilsSpec extends Specification {

    def "declared fields only"() {

        given: "an instance of a class that does not inherit any fields"
        def instance = new Superclass()

        when: "all fields are requested"
        def result = new ReflectionUtils().getAllFields(instance)

        then: "the fields declared by that instance's class are returned"
        result.size() == 1
        result.findAll { it.name in ['superThing'] }.size() == 1
    }


    def "inherited fields"() {

        given: "an instance of a class that inherits fields"
        def instance = new Subclass()

        when: "all fields are requested"
        def result = new ReflectionUtils().getAllFields(instance)

        then: "the fields declared by that instance's class and its superclasses are returned"
        result.size() == 2
        result.findAll { it.name in ['subThing', 'superThing'] }.size() == 2

    }

    def "no fields"() {
        given: "an instance of a class with no declared or inherited fields"
        def instance = new SuperDooperclass()

        when: "all fields are requested"
        def result = new ReflectionUtils().getAllFields(instance)

        then: "the fields declared by that instance's class and its superclasses are returned"
        result.size() == 0
    }

    def "enum"() {

        given: "an instance of an enum"
        def instance = Item.BIT

        when: "all fields are requested"
        def result = new ReflectionUtils().getAllFields(instance)

        then: "the fields declared by that instance's class and its superclasses are returned"
        result.size() == 3
        result.findAll { it.name == 'smallerItem' }.size() == 1
    }

    private class SuperDooperclass {
    }

    private class Superclass extends SuperDooperclass {
        private String superThing
    }


    private class Subclass extends Superclass {
        private String subThing
    }

    private enum Item {

        BIT("quark"), BOB("muon")

        Item(String smallerItem) {
            this.smallerItem = smallerItem
        }

        private String smallerItem

    }
}
Chris
fuente