Truy xuất các tên / giá trị thuộc tính được kế thừa bằng Java Reflection


128

Tôi có một đối tượng Java 'ChildObj' được mở rộng từ 'ParentObj'. Bây giờ, nếu có thể truy xuất tất cả các tên thuộc tính và giá trị của ChildObj, bao gồm cả các thuộc tính được kế thừa, sử dụng cơ chế phản chiếu Java?

Class.getFields cung cấp cho tôi mảng các thuộc tính công khai và Class.getDeclaredFields cung cấp cho tôi mảng của tất cả các trường, nhưng không có trường nào trong số chúng bao gồm danh sách các trường được kế thừa.

Có cách nào để lấy lại các thuộc tính được thừa kế không?

Câu trả lời:


173

không, bạn cần phải tự viết nó Đây là một phương thức đệ quy đơn giản được gọi trên 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
Đúng. nghĩ về điều đó nhưng muốn kiểm tra xem có cách nào khác để làm điều đó không. cảm ơn. :)
Veera

7
Vượt qua một cuộc tranh cãi có thể thay đổi trong và trả lại nó có lẽ không phải là một thiết kế tuyệt vời. Field.add ALL (type.getDeclaredFields ()); sẽ là thông thường hơn là một vòng lặp tăng cường với add.
Tom Hawtin - tackline

Tôi cảm thấy cần ít nhất phải biên dịch nó (trên stackoverflow!) Và có thể thêm vào một chút Arrays.asList.
Tom Hawtin - tackline

Dường như mã của bạn thu thập tất cả các trường, cả trường riêng và trường tĩnh không được kế thừa.
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
Đây là giải pháp ưa thích của tôi, tuy nhiên tôi sẽ gọi nó là "getAllFields" vì nó cũng trả về các trường của lớp đã cho.
Pino

3
Mặc dù tôi rất thích đệ quy (thật thú vị!), Tôi thích tính dễ đọc của phương thức này và các tham số trực quan hơn (không yêu cầu bộ sưu tập mới phải vượt qua), không hơn nếu (ẩn trong mệnh đề for) và không lặp lại trên các trường chúng tôi.
Remi Morin

nó cho thấy đệ quy là không cần thiết và .. tôi thích các mã ngắn! cám ơn! :)
Sức mạnh Bảo Bình

Trong nhiều năm, tôi luôn nghĩ rằng giá trị ban đầu chỉ là một số nguyên, với câu hỏi của @ Veera tôi nghĩ chỉ có đệ quy mới có thể giải quyết nó, @ Esko Luontola lệnh của bạn thật tuyệt vời.
Touya Akira

@Esko: Cảm ơn bạn rất nhiều. Đã cứu sống ngày! Nó súc tích và hoạt động hoàn hảo!
gaurav

37

Thay vào đó, nếu bạn muốn dựa vào thư viện để thực hiện điều này, Apache Commons Lang phiên bản 3.2+ cung cấp 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
Bùng nổ! Tôi yêu không phát minh lại bánh xe. Chúc mừng cho điều này.
Joshua Pinter

6

Bạn cần gọi:

Class.getSuperclass().getDeclaredFields()

Đệ quy phân cấp thừa kế khi cần thiết.


5

Sử dụng thư viện Reflection:

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

4

Các giải pháp đệ quy là OK, vấn đề nhỏ duy nhất là chúng trả về một superset của các thành viên được khai báo và kế thừa. Lưu ý rằng phương thức getDeclaredFields () cũng trả về các phương thức riêng tư. Vì vậy, nếu bạn điều hướng toàn bộ hệ thống phân cấp siêu lớp, bạn sẽ bao gồm tất cả các trường riêng được khai báo trong các siêu lớp và những trường này không được kế thừa.

Một bộ lọc đơn giản với Modifier.isPublic || Vị ngữ Modifier.isProtected sẽ làm:

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

Phiên bản hoạt động của giải pháp "DidYouMeanThatTomHa ..." ở trên


2

Với thư viện mùa xuân, bạn có thể sử dụng để kiểm tra xem một thuộc tính cụ thể có tồn tại trong lớp không:

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

log.info(field2.getName());

Api doc:
https://docs.spring.io/spring-data/commons/docs/civerse/api/org/springframework/data/util/ReflectionUtils.html

hoặc là

 Field field2 = ReflectionUtils.findField(YOUR_CLASS.class, "ATTRIBUTE_NAME");

 log.info(field2.getName());

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

@cheers


1

Bạn co thể thử:

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

1

Ngắn hơn và với ít đối tượng khởi tạo? ^^

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 không thể tìm thấy biểu tượng.
Touya Akira

1
Lớp này là từ Apache Commons Lang.
Alexis LEGROS

Apache đã có chức năng FieldUtils.get ALLFields để xử lý yêu cầu câu hỏi này.
Touya Akira

1

getFields (): Nhận tất cả các trường công khai trên toàn bộ phân cấp lớp và
getDeclaredFields (): Nhận tất cả các trường, bất kể sửa đổi của chúng mà chỉ dành cho lớp hiện tại. Vì vậy, bạn phải có được tất cả các thứ bậc liên quan.
Gần đây tôi đã thấy mã này từ 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

Đây là cách viết lại câu trả lời được chấp nhận bởi @ user1079877. Nó có thể là một phiên bản không sửa đổi một tham số của hàm và cũng sử dụng một số tính năng Java hiện đại.

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

Việc thực hiện này cũng làm cho lời mời ngắn gọn hơn một chút:

var fields = getFields(MyType.class);

0

Có một vài điều kỳ quặc không được FieldUtils giải quyết - các trường tổng hợp cụ thể (ví dụ như được JaCoCo đưa vào) và thực tế là một loại enum tất nhiên có một trường cho mỗi trường hợp và nếu bạn đang duyệt qua biểu đồ đối tượng, hãy lấy tất cả các trường và sau đó nhận các trường của từng trường, v.v., sau đó bạn sẽ vào một vòng lặp vô hạn khi bạn nhấn enum. Một giải pháp mở rộng (và thành thật mà nói Tôi chắc chắn rằng điều này phải sống trong một thư viện ở đâu đó!) Sẽ là:

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

Lớp kiểm tra trong Spock (và Groovy thêm các trường tổng hợp):

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

    }
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.