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?

Veera
sumber

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));
}
dfa
sumber
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;
    }
Esko Luontola
sumber
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));
    }
}
Chris
sumber
6
Ledakan! Saya suka tidak menciptakan kembali roda. Sorakan untuk ini.
Joshua Pinter
6

Anda perlu menelepon:

Class.getSuperclass().getDeclaredFields()

Mengulang hierarki warisan seperlunya.

Nick Holt
sumber
5

Gunakan perpustakaan Refleksi:

public Set<Field> getAllFields(Class<?> aClass) {
    return org.reflections.ReflectionUtils.getAllFields(aClass);
}
Lukasz Ochmanski
sumber
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);
    }
}
Marek Dec
sumber
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

Theo Platt
sumber
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

Marcelo Rebouças
sumber
1

Anda dapat mencoba:

   Class parentClass = getClass().getSuperclass();
   if (parentClass != null) {
      parentClass.getDeclaredFields();
   }
Manuel Selva
sumber
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();
}
Alexis LEGROS
sumber
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;
}
Seorang pria
sumber
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
sumber
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);
scrutari
sumber
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

    }
}
Chris
sumber