Є кілька химер, на які FieldUtils не звертається - зокрема синтетичні поля (наприклад, введені JaCoCo), а також той факт, що тип 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);
}
Тестовий клас у Споку (і 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
}
}