Recuperando os nomes / valores de atributos herdados usando o Java Reflection

128

Eu tenho um objeto Java 'ChildObj' que é estendido de 'ParentObj'. Agora, se é possível recuperar todos os nomes e valores de atributos de ChildObj, incluindo também os atributos herdados, usando o mecanismo de reflexão Java?

Class.getFields fornece a matriz de atributos públicos e Class.getDeclaredFields fornece a matriz de todos os campos, mas nenhum deles inclui a lista de campos herdados.

Existe alguma maneira de recuperar os atributos herdados também?

Veera
fonte

Respostas:

173

não, você precisa escrever você mesmo. É um método recursivo simples chamado 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
fonte
2
sim. pensei sobre isso. mas queria verificar se existe outra maneira de fazer isso. obrigado. :)
Veera
7
Passar e devolver um argumento mutável provavelmente não é um ótimo design. fields.addAll (type.getDeclaredFields ()); seria mais convencional do que um loop for aprimorado com add.
Tom Hawtin - tackline
Eu sentiria a necessidade de pelo menos compilá-lo (no stackoverflow!) E provavelmente adicionar um pouco de Arrays.asList.
Tom Hawtin - tackline
Parece que seu código coleta todos os campos, também campos privados e estáticos que não são herdados.
Peter Verhas 12/02/19
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
fonte
9
Esta é a minha solução preferida, no entanto, eu chamaria de "getAllFields" porque retorna os campos da classe especificada também.
Pino
3
Embora eu goste muito de recursividade (é divertido!), Prefiro a legibilidade desse método e os parâmetros mais intuitivos (não é necessário que uma nova coleção seja aprovada), nem mais se (implícita na cláusula for) e nenhuma iteração sobre os campos si mesmos.
Remi Morin
mostra que recursivo é desnecessário e ... eu gosto de códigos curtos! THX! :)
Aquarius Power
Em muitos anos, eu sempre acho que o valor inicial é apenas um número inteiro, com a pergunta de @ Veera, acho que apenas a recursiva pode resolvê-lo, @ Esko Luontola, seu comando é incrível.
Touya Akira
@ Esko: Muito obrigado. Salvou o dia! É conciso e funciona perfeitamente!
gaurav
37

Se, em vez disso, você quis contar com uma biblioteca para fazer isso, o Apache Commons Lang versão 3.2+ fornece 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
fonte
6
Estrondo! Eu amo não reinventar a roda. Um brinde por isso.
27414 Joshua Pinter
6

Você precisa ligar para:

Class.getSuperclass().getDeclaredFields()

Recursando a hierarquia de herança, conforme necessário.

Nick Holt
fonte
5

Use a biblioteca de Reflexões:

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

As soluções recursivas estão OK, o único pequeno problema é que elas retornam um superconjunto de membros declarados e herdados. Observe que o método getDeclaredFields () retorna também métodos privados. Portanto, considerando que você navega por toda a hierarquia da superclasse, incluirá todos os campos particulares declarados nas superclasses e esses não serão herdados.

Um filtro simples com um Modifier.isPublic || O predicado Modifier.isProtected faria:

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

Versão de trabalho da solução "DidYouMeanThatTomHa ..." acima

Theo Platt
fonte
2

Com a biblioteca util do spring, você pode usar para verificar se existe um atributo específico na classe:

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

log.info(field2.getName());

Api doc:
https://docs.spring.io/spring-data/commons/docs/current/api/org/springframework/data/util/ReflectionUtils.html

ou

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

 log.info(field2.getName());

Api doc:
https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/util/ReflectionUtils.html

@Felicidades

Marcelo Rebouças
fonte
1

Podes tentar:

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

Mais curto e com 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
fonte
HI @Alexis LEGROS: ArrayUtils não consegue encontrar o símbolo.
Touya Akira
1
Esta classe é do Apache Commons Lang.
Alexis LEGROS
O Apache já possui uma função FieldUtils.getAllFields para lidar com essa solicitação de pergunta.
Touya Akira
1

getFields (): Obtém todos os campos públicos em toda a hierarquia de classes e
getDeclaredFields (): Obtém todos os campos, independentemente de seus modificadores, mas apenas para a classe atual. Então, você precisa obter toda a hierarquia envolvida.
Vi recentemente esse código em 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;
}
Um homem
fonte
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
fonte
0

Esta é uma reformulação da resposta aceita por @ user1079877. Pode ser que uma versão que não modifique um parâmetro da função e também use alguns recursos Java modernos.

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

Essa implementação também torna a chamada um pouco mais concisa:

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

Existem algumas peculiaridades que não são tratadas pelo FieldUtils - especificamente campos sintéticos (por exemplo, injetados pela JaCoCo) e também o fato de que um tipo de enum possui um campo para cada instância e, se você estiver percorrendo um gráfico de objeto, obtém todos os campos e, em seguida, obtendo os campos de cada um deles, etc., você entrará em um loop infinito quando atingir um enum. Uma solução estendida (e para ser sincero, tenho certeza de que isso deve estar em uma biblioteca em algum lugar!) Seria:

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

Classe de teste no Spock (e o Groovy adiciona 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
fonte