Existem algumas peculiaridades que não são tratadas pelo FieldUtils - especificamente campos sintéticos (por exemplo, injetados pela JaCoCo) e também o fato de que um tipo de enum possui um campo para cada instância e, se você estiver percorrendo um gráfico de objeto, obtém todos os campos e, em seguida, obtendo os campos de cada um deles, etc., você entrará em um loop infinito quando atingir um enum. Uma solução estendida (e para ser sincero, tenho certeza de que isso deve estar em uma biblioteca em algum lugar!) Seria:
/**
* 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);
}
Classe de teste no Spock (e o Groovy adiciona campos sintéticos):
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
}
}