Recupero dei nomi / valori degli attributi ereditati utilizzando Java Reflection


128

Ho un oggetto Java 'ChildObj' che è esteso da 'ParentObj'. Ora, se è possibile recuperare tutti i nomi e i valori degli attributi di ChildObj, inclusi anche gli attributi ereditati, usando il meccanismo di riflessione Java?

Class.getFields mi dà l'array di attributi pubblici e Class.getDeclaredFields mi dà l'array di tutti i campi, ma nessuno di essi include l'elenco dei campi ereditati.

Esiste un modo per recuperare anche gli attributi ereditati?

Risposte:


173

no, devi scriverlo tu stesso. È un semplice metodo ricorsivo chiamato su 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
sì. ci ho pensato. ma volevo verificare se esiste un altro modo per farlo. Grazie. :)
Veera,

7
Passare un argomento mutevole e restituirlo probabilmente non è un grande progetto. fields.addAll (type.getDeclaredFields ()); sarebbe più convenzionale di un loop for avanzato con add.
Tom Hawtin: affronta il

Sentirei la necessità almeno di compilarlo (su stackoverflow!), E probabilmente aggiungerei un po 'di Arrays.asList.
Tom Hawtin: affronta il

Sembra che il tuo codice raccolga tutti i campi, anche i campi privati ​​e statici che non sono ereditati.
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
Questa è la mia soluzione preferita, tuttavia la chiamerei "getAllFields" perché restituisce anche i campi di una determinata classe.
Pino,

3
Sebbene mi piaccia molto la ricorsività (è divertente!), Preferisco la leggibilità di questo metodo e i parametri più intuitivi (non è necessario il passaggio di una nuova raccolta), non più se (implicito nella clausola for) e nessuna iterazione sui campi loro stessi.
Remi Morin,

mostra ricorsivo non è necessario e .. mi piacciono i codici funzione! grazie! :)
Aquarius Power

In molti anni penso sempre che il valore iniziale in per sia solo un numero intero, con la domanda di @ Veera penso che solo ricorsivo possa risolverlo, @ Esko Luontola il tuo comando è fantastico.
Touya Akira,

@Esko: grazie mille. Salvato il giorno! È conciso e funziona perfettamente!
gaurav,

37

Se invece volevi fare affidamento su una libreria per raggiungere questo obiettivo, Apache Commons Lang versione 3.2+ fornisce 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! Adoro non reinventare la ruota. Saluti per questo.
Joshua Pinter,

6

Devi chiamare:

Class.getSuperclass().getDeclaredFields()

Ricorrendo la gerarchia ereditaria, se necessario.


5

Usa la libreria Reflections:

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

4

Le soluzioni ricorsive sono OK, l'unico piccolo problema è che restituiscono un superset di membri dichiarati ed ereditati. Si noti che il metodo getDeclaredFields () restituisce anche metodi privati. Quindi dato che navighi nell'intera gerarchia di superclassi includerai tutti i campi privati ​​dichiarati nelle superclassi e quelli non verranno ereditati.

Un semplice filtro con un Modifier.isPublic || Il predicato Modifier.isProtected farebbe:

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

Versione di lavoro della soluzione "DidYouMeanThatTomHa ..." sopra


2

Con la libreria util di primavera, puoi usare per verificare se esiste un attributo specifico nella 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

o

 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

@Saluti


1

Puoi provare:

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

1

Più breve e con meno oggetti istanziati? ^^

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 non riesce a trovare il simbolo.
Touya Akira,

1
Questa lezione è di Apache Commons Lang.
Alexis LEGROS,

Apache ha già una funzione FieldUtils.getAllFields per gestire questa richiesta di domanda.
Touya Akira,

1

getFields (): ottiene tutti i campi pubblici nell'intera gerarchia di classi e
getDeclaredFields (): ottiene tutti i campi, indipendentemente dai loro modificatori ma solo per la classe corrente. Quindi, devi ottenere per tutta la gerarchia coinvolta.
Recentemente ho visto questo codice da 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

Questa è una riformulazione della risposta accettata da @ user1079877. Potrebbe essere una versione che non modifica un parametro della funzione e utilizza anche alcune moderne funzionalità 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);
    }
}

Questa implementazione rende anche l'invocazione un po 'più concisa:

var fields = getFields(MyType.class);

0

Ci sono un paio di stranezze che non sono affrontate da FieldUtils - in particolare campi sintetici (ad esempio iniettati da JaCoCo) e anche il fatto che un tipo di enum ovviamente ha un campo per ogni istanza, e se stai attraversando un grafico a oggetti, ottenendo tutti i campi e quindi ottenere i campi di ciascuno di essi ecc., allora entrerai in un ciclo infinito quando colpisci un enum. Una soluzione estesa (e ad essere sincero sono sicuro che questo debba vivere in una biblioteca da qualche parte!) Sarebbe:

/**
 * 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 di test in Spock (e Groovy aggiunge campi sintetici):

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

    }
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.