Recuperar campos de uma classe Java usando reflexão
1. Visão geral
Reflection é a capacidade do software de computador de inspecionar sua estrutura em tempo de execução. Em Java, conseguimos isso usandoJava Reflection API. Ele nos permite inspecionar os elementos de uma classe, como campos, métodos ou mesmo classes internas, tudo em tempo de execução.
Este tutorial se concentrará em como recuperar os campos de uma classe Java, incluindo campos privados e herdados.
2. Recuperando campos de uma classe
Vamos primeiro dar uma olhada em como recuperar os campos de uma classe, independentemente de sua visibilidade. Mais tarde, veremos como obter campos herdados também.
Vamos começar com um exemplo de uma classePerson com dois camposString:lastNameefirstName. O primeiro éprotected (que será útil mais tarde), enquanto o último éprivate:
public class Person {
protected String lastName;
private String firstName;
}
Queremos obter os camposlastName efirstName usando reflexão. We’ll achieve this by using the Class::getDeclaredFields method. As its name suggests, this returns all the declared fields of a class, in the form of a Field array:
public class PersonAndEmployeeReflectionUnitTest {
/* ... constants ... */
@Test
public void givenPersonClass_whenGetDeclaredFields_thenTwoFields() {
Field[] allFields = Person.class.getDeclaredFields();
assertEquals(2, allFields.length);
assertTrue(Arrays.stream(allFields).anyMatch(field ->
field.getName().equals(LAST_NAME_FIELD)
&& field.getType().equals(String.class))
);
assertTrue(Arrays.stream(allFields).anyMatch(field ->
field.getName().equals(FIRST_NAME_FIELD)
&& field.getType().equals(String.class))
);
}
}
Como podemos ver, obtemos os dois campos da classePerson. Verificamos seus nomes e tipos que correspondem às definições de campos na classePerson.
3. Recuperando campos herdados
Vamos agora ver como obter os campos herdados de uma classe Java.
Para ilustrar isso, vamos criar uma segunda classe chamadaEmployee estendendoPerson, com um campo próprio:
public class Employee extends Person {
public int employeeId;
}
3.1. Recuperando campos herdados em uma hierarquia de classes simples
Using Employee.class.getDeclaredFields() would only return the employeeId field, pois este método não retorna os campos declarados nas superclasses. Para obter campos herdados, também devemos obter os campos da superclassePerson.
Obviamente, poderíamos usar o métodogetDeclaredFields() em ambas as classesPerson andEmployee e mesclar seus resultados em uma única matriz. Mas e se não quisermos especificar explicitamente a superclasse?
Nesse caso,we can make use of another method of the Java Reflection API: Class::getSuperclass. Isso nos dá a superclasse de outra classe, sem precisarmos saber o que é essa superclasse.
Vamos reunir os resultados degetDeclaredFields() emEmployee.classeEmployee.class.getSuperclass()e mesclá-los em uma única matriz:
@Test
public void givenEmployeeClass_whenGetDeclaredFieldsOnBothClasses_thenThreeFields() {
Field[] personFields = Employee.class.getSuperclass().getDeclaredFields();
Field[] employeeFields = Employee.class.getDeclaredFields();
Field[] allFields = new Field[employeeFields.length + personFields.length];
Arrays.setAll(allFields, i ->
(i < personFields.length ? personFields[i] : employeeFields[i - personFields.length]));
assertEquals(3, allFields.length);
Field lastNameField = allFields[0];
assertEquals(LAST_NAME_FIELD, lastNameField.getName());
assertEquals(String.class, lastNameField.getType());
Field firstNameField = allFields[1];
assertEquals(FIRST_NAME_FIELD, firstNameField.getName());
assertEquals(String.class, firstNameField.getType());
Field employeeIdField = allFields[2];
assertEquals(EMPLOYEE_ID_FIELD, employeeIdField.getName());
assertEquals(int.class, employeeIdField.getType());
}
Podemos ver aqui que reunimos os dois campos dePerson , bem como o único campo deEmployee.
Mas, o campoprivate dePerson é realmente um campo herdado? Não muito. Isso seria o mesmo para um campopackage-private. Only public and protected fields are considered inherited.
3.2. Filtrando campospublic eprotected
Infelizmente, nenhum método na API Java nos permite reunir campospublic andprotected de uma classe e suas superclasses. O métodoClass::getFields se aproxima de nosso objetivo, pois retorna todos os campospublic de uma classe e suas superclasses, mas não osprotected.
A única maneira de obter apenas os campos herdados é usar o métodogetDeclaredFields(), como acabamos de fazer, e filtrar seus resultados usando o métodoField::getModifiers . This one returns an int representing the modifiers of the current field. Each possible modifier is assigned a power of two between 2^0 and 2^7.
Por exemplo,public é2^0estatic é2^3. Portanto, chamar o métodogetModifiers() em um campopublic andstatic retornaria 9.
Então, é possível realizar umbitwise and entre este valor e o valor de um modificador específico para ver se esse campo tem esse modificador. Se a operação retornar algo diferente de 0, o modificador será aplicado, caso contrário não.
Temos sorte, pois o Java nos fornece uma classe de utilitário para verificar se os modificadores estão presentes no valor retornado porgetModifiers(). Let’s use the isPublic() and isProtected() methods to gather only inherited fields in our example:
List personFields = Arrays.stream(Employee.class.getSuperclass().getDeclaredFields())
.filter(f -> Modifier.isPublic(f.getModifiers()) || Modifier.isProtected(f.getModifiers()))
.collect(Collectors.toList());
assertEquals(1, personFields.size());
assertTrue(personFields.stream().anyMatch(field ->
field.getName().equals(LAST_NAME_FIELD)
&& field.getType().equals(String.class))
);
Como podemos ver, o resultado não carrega mais o campoprivate.
3.3. Recuperando campos herdados em uma hierarquia de classe profunda
No exemplo acima, trabalhamos em uma única hierarquia de classes. O que fazemos agora se tivermos uma hierarquia de classes mais profunda e quisermos reunir todos os campos herdados?
Vamos supor que temos uma subclasse deEmployee ou uma superclasse dePerson –, então obter os campos de toda a hierarquia exigirá a verificação de todas as superclasses.
Podemos conseguir isso criando um método utilitário que percorre a hierarquia, criando o resultado completo para nós:
List getAllFields(Class clazz) {
if (clazz == null) {
return Collections.emptyList();
}
List result = new ArrayList<>(getAllFields(clazz.getSuperclass()));
List filteredFields = Arrays.stream(clazz.getDeclaredFields())
.filter(f -> Modifier.isPublic(f.getModifiers()) || Modifier.isProtected(f.getModifiers()))
.collect(Collectors.toList());
result.addAll(filteredFields);
return result;
}
Este método recursivo pesquisará os campospubliceprotected através da hierarquia de classes e retornará todos os que foram encontrados emList.
Vamos ilustrar isso com um pequeno teste em uma nova classeMonthEmployee, estendendo aEmployee:
public class MonthEmployee extends Employee {
protected double reward;
}
Esta classe define um novo campo -reward. Definições deGiven all the hierarchy class, our method should give us the following fields:Person::lastName, Employee::employeeId eMonthEmployee::reward.
Vamos chamar o métodogetAllFields() emMonthEmployee:
@Test
public void givenMonthEmployeeClass_whenGetAllFields_thenThreeFields() {
List allFields = getAllFields(MonthEmployee.class);
assertEquals(3, allFields.size());
assertTrue(allFields.stream().anyMatch(field ->
field.getName().equals(LAST_NAME_FIELD)
&& field.getType().equals(String.class))
);
assertTrue(allFields.stream().anyMatch(field ->
field.getName().equals(EMPLOYEE_ID_FIELD)
&& field.getType().equals(int.class))
);
assertTrue(allFields.stream().anyMatch(field ->
field.getName().equals(MONTH_EMPLOYEE_REWARD_FIELD)
&& field.getType().equals(double.class))
);
}
Como esperado, reunimos todos os campospubliceprotected.
4. Conclusão
Neste artigo, vimos como recuperar os campos de uma classe Java usandoJava Reflection API.
Primeiro aprendemos como recuperar os campos declarados de uma classe. Depois disso, vimos como recuperar seus campos de superclasse também. Em seguida, aprendemos a filtrar campos não -publice não -protected.
Por fim, vimos como aplicar tudo isso para reunir os campos herdados de uma hierarquia de várias classes.
Como de costume, o código completo deste artigo está disponívelover on our GitHub.