การดึงชื่อ / ค่าของแอตทริบิวต์ที่สืบทอดมาโดยใช้ Java Reflection


128

ฉันเป็นวัตถุ Java 'ChildObj' ซึ่งขยายจาก 'ParentObj' ตอนนี้ถ้าเป็นไปได้ที่จะดึงชื่อคุณลักษณะและค่าทั้งหมดของ ChildObj รวมถึงคุณลักษณะที่สืบทอดมาด้วยโดยใช้กลไกการสะท้อน Java?

Class.getFieldsให้อาเรย์ของคุณลักษณะสาธารณะและClass.getDeclaredFieldsมอบอาเรย์ของฟิลด์ทั้งหมดให้ฉัน แต่ไม่มีรายการใดรวมถึงรายการฟิลด์ที่สืบทอด

มีวิธีใดที่จะดึงข้อมูลคุณลักษณะที่สืบทอดมาด้วยหรือไม่

คำตอบ:


173

ไม่คุณต้องเขียนด้วยตัวเอง มันเป็นวิธีการเรียกซ้ำง่าย ๆ ที่เรียกว่า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
ใช่. คิดเกี่ยวกับเรื่องนั้น แต่ต้องการตรวจสอบว่ามีวิธีอื่นใดอีกหรือไม่ที่จะทำเช่นนั้น ขอบคุณ :)
Veera

7
ผ่านการโต้แย้งไม่แน่นอนในและกลับมามันอาจจะไม่ใช่การออกแบบที่ดี fields.addAll (type.getDeclaredFields ()); จะธรรมดากว่าการปรับปรุงสำหรับวนกับเพิ่ม
Tom Hawtin - tackline

ฉันรู้สึกว่าจำเป็นต้องรวบรวมอย่างน้อย (ใน stackoverflow!) และอาจเพิ่มใน Arrays.asList เล็กน้อย
Tom Hawtin - tackline

ดูเหมือนว่ารหัสของคุณจะรวบรวมเขตข้อมูลทั้งหมดรวมถึงเขตข้อมูลส่วนบุคคลและเขตคงที่ที่ไม่ได้รับมรดก
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
นี่คือทางออกที่ฉันต้องการ แต่ฉันจะเรียกมันว่า "getAllFields" เพราะมันจะคืนค่าฟิลด์ของคลาสที่กำหนดด้วย
Pino

3
แม้ว่าฉันจะชอบ recursivity มาก (สนุกมาก!) ฉันชอบความสามารถในการอ่านของวิธีนี้และพารามิเตอร์ที่ใช้งานง่ายขึ้น (ไม่จำเป็นต้องมีคอลเลกชันใหม่ที่จะผ่าน) ไม่มากถ้า (โดยนัยในส่วนคำสั่ง) และไม่ซ้ำ ตัวเอง
Remi Morin

มันแสดงให้เห็นซ้ำไม่จำเป็นและ .. ฉันชอบรหัสสั้น ๆ ! ขอบคุณ! :)
กุมภ์ Power

ในหลายปีที่ผ่านมาฉันคิดว่าค่าเริ่มต้นในนั้นเป็นเพียงจำนวนเต็มด้วยคำถามของ @ Veera ฉันคิดว่ามีเพียง recursive เท่านั้นที่สามารถแก้ปัญหานี้ได้ @ Esko Luontola คำสั่งของคุณยอดเยี่ยม
Touya Akira

@Esko: ขอบคุณมาก บันทึกวัน! มันกระชับและทำงานได้อย่างไร้ที่ติ!
gaurav

37

หากคุณต้องการใช้ห้องสมุดเพื่อทำสิ่งนี้แทนApache Commons Langเวอร์ชัน 3.2+ จะให้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
บูม! ฉันชอบที่จะไม่สร้างวงล้อใหม่ ขอบคุณสำหรับสิ่งนี้
Joshua Pinter



4

โซลูชันแบบเรียกซ้ำไม่เป็นไรปัญหาเล็ก ๆ เพียงอย่างเดียวคือพวกเขาคืนค่าสมาชิกที่ได้รับการประกาศและสืบทอดมา โปรดสังเกตว่าเมธอด getDeclaredFields () จะส่งคืนเมธอดส่วนตัวด้วย ดังนั้นเมื่อคุณนำทางลำดับชั้นซูเปอร์คลาสทั้งหมดคุณจะต้องรวมฟิลด์ส่วนตัวทั้งหมดที่ประกาศในซูเปอร์คลาสและสิ่งที่ไม่ได้รับการสืบทอด

ตัวกรองอย่างง่ายพร้อม Modifier.isPublic || ภาคแสดง Modifier.isProtected จะทำ:

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

รุ่นทำงานของ "DidYouMeanThatTomHa ... " โซลูชันด้านบน


2

ด้วย spring util ไลบรารี่คุณสามารถใช้เพื่อตรวจสอบว่ามีคุณลักษณะเฉพาะอยู่ในคลาสหรือไม่:

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

หรือ

 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

@cheers



1

สั้นลงและมีวัตถุน้อยลง ^^

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 ไม่พบสัญลักษณ์
Touya Akira

1
คลาสนี้มาจาก Apache Commons Lang
Alexis LEGROS

Apache มีฟังก์ชัน FieldUtils.getAllFields อยู่แล้วเพื่อจัดการคำขอคำถามนี้
Touya Akira

1

getFields (): รับค่าฟิลด์สาธารณะทั้งหมดตามลำดับชั้นของคลาสทั้งหมดและ
getDeclaredFields (): รับค่าฟิลด์ทั้งหมดโดยไม่คำนึงถึงตัวดัดแปลง แต่สำหรับคลาสปัจจุบันเท่านั้น ดังนั้นคุณต้องรับทุกลำดับชั้นที่เกี่ยวข้อง
ฉันเพิ่งเห็นรหัสนี้จาก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

นี่คือการตอบกลับใหม่ของคำตอบที่ยอมรับโดย @ user1079877 อาจเป็นได้ว่ารุ่นที่ไม่ได้แก้ไขพารามิเตอร์ของฟังก์ชั่นและยังใช้คุณสมบัติ 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);
    }
}

การใช้งานนี้ยังทำให้การเรียกใช้มีความรัดกุมขึ้นอีกเล็กน้อย:

var fields = getFields(MyType.class);

0

มี quirks สองสามอย่างที่ไม่ได้รับการจัดการโดย FieldUtils - ฟิลด์สังเคราะห์เฉพาะ (เช่นการฉีดโดย JaCoCo) และความจริงที่ว่าประเภท Enum ของหลักสูตรมีฟิลด์สำหรับแต่ละอินสแตนซ์และหากคุณกำลังข้ามกราฟวัตถุการรับ เขตข้อมูลทั้งหมดแล้วรับเขตข้อมูลของแต่ละเขตข้อมูล ฯลฯ จากนั้นคุณจะเข้าสู่วงวนไม่สิ้นสุดเมื่อคุณกด enum โซลูชันเพิ่มเติม (และด้วยความซื่อสัตย์ฉันแน่ใจว่าสิ่งนี้จะต้องอยู่ในห้องสมุดแห่งหนึ่ง!) จะเป็น:

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

คลาสทดสอบใน Spock (และ Groovy เพิ่มเขตข้อมูลสังเคราะห์):

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

    }
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.