Récupération des noms / valeurs d'attributs hérités à l'aide de Java Reflection


128

J'ai un objet Java 'ChildObj' qui est étendu de 'ParentObj'. Maintenant, s'il est possible de récupérer tous les noms et valeurs d'attribut de ChildObj, y compris les attributs hérités également, en utilisant le mécanisme de réflexion Java?

Class.getFields me donne le tableau des attributs publics et Class.getDeclaredFields me donne le tableau de tous les champs, mais aucun d'entre eux n'inclut la liste des champs hérités.

Existe-t-il un moyen de récupérer également les attributs hérités?

Réponses:


173

non, vous devez l'écrire vous-même. C'est une méthode récursive simple appelée sur 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));
}

2
Oui. pensé à cela. mais je voulais vérifier s'il existe un autre moyen de le faire. Merci. :)
Veera

7
Passer un argument mutable et le renvoyer n'est probablement pas une excellente conception. fields.addAll (type.getDeclaredFields ()); serait plus conventionnelle qu'une boucle for améliorée avec add.
Tom Hawtin - tackline

Je ressentirais le besoin au moins de le compiler (sur stackoverflow!), Et probablement d'ajouter un petit Arrays.asList.
Tom Hawtin - tackline

Il semble que votre code collecte tous les champs, également les champs privés et statiques qui ne sont pas hérités.
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;
    }

9
C'est ma solution préférée, mais je l'appellerais "getAllFields" car elle renvoie également les champs de la classe donnée.
Pino

3
Bien que j'aime beaucoup la récursivité (c'est amusant!), Je préfère la lisibilité de cette méthode et les paramètres plus intuitifs (pas besoin d'une nouvelle collection à passer), pas plus de if (implicite dans la clause for) et aucune itération sur les champs se.
Remi Morin

ça montre que récursif est inutile et .. j'aime les codes courts! THX! :)
Aquarius Power

Depuis de nombreuses années, je pense toujours que la valeur initiale de for n'est qu'un entier, avec la question de @ Veera, je pense que seule la récursive peut la résoudre, @ Esko Luontola votre commande est géniale.
Touya Akira le

@Esko: Merci beaucoup. Sauvé la journée! C'est concis et fonctionne parfaitement!
gaurav le

37

Si à la place vous vouliez vous fier à une bibliothèque pour ce faire, Apache Commons Lang version 3.2+ fournit 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));
    }
}

6
Boom! J'adore ne pas réinventer la roue. Bravo pour cela.
Joshua Pinter

6

Vous devez appeler:

Class.getSuperclass().getDeclaredFields()

Réexécution de la hiérarchie d'héritage si nécessaire.


5

Utilisez la bibliothèque Reflections:

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

4

Les solutions récursives sont OK, le seul petit problème est qu'elles renvoient un sur-ensemble de membres déclarés et hérités. Notez que la méthode getDeclaredFields () renvoie également des méthodes privées. Donc, étant donné que vous parcourez toute la hiérarchie des superclasses, vous incluerez tous les champs privés déclarés dans les superclasses, et ceux-ci ne seront pas hérités.

Un filtre simple avec un Modifier.isPublic || Le prédicat Modifier.isProtected ferait:

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

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

Version de travail de la solution "DidYouMeanThatTomHa ..." ci-dessus


2

Avec la bibliothèque Spring util, vous pouvez utiliser pour vérifier si un attribut spécifique existe dans la classe:

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

log.info(field2.getName());

Doc Api:
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());

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

@à votre santé


1

Tu peux essayer:

   Class parentClass = getClass().getSuperclass();
   if (parentClass != null) {
      parentClass.getDeclaredFields();
   }

1

Plus court et avec moins d'objets instanciés? ^^

private static Field[] getAllFields(Class<?> type) {
    if (type.getSuperclass() != null) {
        return (Field[]) ArrayUtils.addAll(getAllFields(type.getSuperclass()), type.getDeclaredFields());
    }
    return type.getDeclaredFields();
}

HI @Alexis LEGROS: ArrayUtils ne trouve pas le symbole.
Touya Akira

1
Cette classe est issue d'Apache Commons Lang.
Alexis LEGROS

Apache a déjà une fonction FieldUtils.getAllFields pour gérer cette demande de question.
Touya Akira

1

getFields (): récupère tous les champs publics dans toute la hiérarchie de classe et
getDeclaredFields (): récupère tous les champs, quels que soient leurs modificateurs mais uniquement pour la classe actuelle. Donc, vous devez obtenir pour toute la hiérarchie impliquée.
J'ai récemment vu ce code 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;
}

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

0

Ceci est une reformulation de la réponse acceptée par @ user1079877. Il peut s'agir d'une version qui ne modifie pas un paramètre de la fonction et utilise également certaines fonctionnalités Java modernes.

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

Cette implémentation rend également l'appel un peu plus concis:

var fields = getFields(MyType.class);

0

Il y a quelques bizarreries qui ne sont pas abordées par FieldUtils - spécifiquement les champs synthétiques (par exemple injectés par JaCoCo) et aussi le fait qu'un type enum a bien sûr un champ pour chaque instance, et si vous parcourez un graphe d'objets, obtenir tous les champs, puis obtenir les champs de chacun d'eux, etc., vous entrerez dans une boucle infinie lorsque vous frapperez une énumération. Une solution étendue (et pour être honnête, je suis sûr que cela doit vivre dans une bibliothèque quelque part!) Serait:

/**
 * 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 test dans Spock (et Groovy ajoute des champs synthétiques):

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

    }
}
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.