Mengambil nama / nilai atribut yang diwarisi menggunakan Java Reflection


128

Saya punya objek Java 'ChildObj' yang diperpanjang dari 'ParentObj'. Sekarang, apakah mungkin untuk mengambil semua nama atribut dan nilai-nilai ChildObj, termasuk atribut yang diwarisi juga, menggunakan mekanisme refleksi Java?

Class.getFields memberi saya array atribut publik, dan Class.getDeclaredFields memberi saya array dari semua bidang, tetapi tidak ada yang menyertakan daftar bidang yang diwarisi.

Apakah ada cara untuk mengambil atribut yang diwarisi juga?

Jawaban:


173

tidak, Anda harus menulisnya sendiri. Ini adalah metode rekursif sederhana yang dipanggil di 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
Iya. memikirkan itu. tetapi ingin memeriksa apakah ada cara lain untuk melakukan itu. Terima kasih. :)
Veera

7
Melewati argumen yang bisa berubah dalam dan mengembalikannya mungkin bukan desain yang bagus. fields.addAll (type.getDeclaredFields ()); akan lebih konvensional daripada yang ditingkatkan untuk loop dengan add.
Tom Hawtin - tackline

Saya merasa perlu untuk setidaknya mengkompilasinya (pada stackoverflow!), Dan mungkin menambahkan sedikit Array.asList.
Tom Hawtin - tackline

Tampaknya kode Anda mengumpulkan semua bidang, juga bidang pribadi dan statis yang tidak diwariskan.
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
Ini adalah solusi pilihan saya, namun saya akan menyebutnya "getAllFields" karena mengembalikan bidang kelas yang diberikan juga.
Pino

3
Walaupun saya sangat suka recursivity (ini menyenangkan!), Saya lebih suka keterbacaan metode ini dan parameter yang lebih intuitif (tidak memerlukan koleksi baru untuk dilewati), tidak lebih jika (tersirat dalam untuk klausa) dan tidak ada iterasi pada bidang diri.
Remi Morin

itu menunjukkan rekursif tidak perlu dan .. Saya suka kode pendek! Terima kasih! :)
Aquarius Power

Dalam bertahun-tahun saya selalu berpikir nilai awal untuk hanyalah integer, dengan pertanyaan @ Veera saya pikir hanya rekursif yang dapat menyelesaikannya, @ Esko Luontola perintah Anda luar biasa.
Touya Akira

@ Esko: Terima kasih banyak. Selamat hari itu! Ini ringkas dan bekerja dengan sempurna!
gaurav

37

Jika Anda ingin mengandalkan perpustakaan untuk melakukannya, Apache Commons Lang versi 3.2+ menyediakan 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
Ledakan! Saya suka tidak menciptakan kembali roda. Sorakan untuk ini.
Joshua Pinter

6

Anda perlu menelepon:

Class.getSuperclass().getDeclaredFields()

Mengulang hierarki warisan seperlunya.


5

Gunakan perpustakaan Refleksi:

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

4

Solusi rekursif OK, satu-satunya masalah kecil adalah bahwa mereka mengembalikan superset anggota yang dinyatakan dan diwarisi. Perhatikan bahwa metode getDeclaredFields () mengembalikan juga metode pribadi. Jadi mengingat bahwa Anda menavigasi seluruh hierarki superclass, Anda akan menyertakan semua bidang pribadi yang dideklarasikan dalam superclasses, dan itu tidak diwariskan.

Filter sederhana dengan Modifier.isPublic || Predikat Modifier.isProtected akan melakukan:

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

Versi yang berfungsi dari solusi "DidYouMeanThatTomHa ..." di atas


2

Dengan pustaka util spring, Anda dapat menggunakan untuk memeriksa apakah satu atribut spesifik ada di kelas:

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

atau

 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

@Bersulang


1

Anda dapat mencoba:

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

1

Lebih pendek dan lebih sedikit objek yang dipakai? ^^

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 tidak dapat menemukan simbol.
Touya Akira

1
Kelas ini dari Apache Commons Lang.
Alexis LEGROS

Apache sudah memiliki fungsi FieldUtils.getAllFields untuk menangani permintaan pertanyaan ini.
Touya Akira

1

getFields (): Mendapat semua bidang publik naik seluruh hierarki kelas dan
getDeclaredFields (): Mendapat semua bidang, terlepas dari pengubahnya tetapi hanya untuk kelas saat ini. Jadi, Anda harus mendapatkan semua hierarki yang terlibat.
Baru-baru ini saya melihat kode ini dari 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

Ini adalah penulisan ulang jawaban yang diterima oleh @ user1079877. Mungkin versi yang tidak mengubah parameter fungsi dan juga menggunakan beberapa fitur Java modern.

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

Implementasi ini juga membuat doa lebih ringkas:

var fields = getFields(MyType.class);

0

Ada beberapa kebiasaan yang tidak ditangani oleh FieldUtils - khususnya bidang sintetik (misalnya disuntikkan oleh JaCoCo) dan juga fakta bahwa jenis enum tentu saja memiliki bidang untuk setiap contoh, dan jika Anda menelusuri grafik objek, mendapatkan semua bidang dan kemudian mendapatkan bidang masing-masing dll, maka Anda akan masuk ke loop tak terbatas ketika Anda menekan enum. Solusi yang diperluas (dan sejujurnya saya yakin ini harus tinggal di perpustakaan di suatu tempat!) Adalah:

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

Kelas uji di Spock (dan Groovy menambahkan bidang sintetis):

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

    }
}
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.