Quantcast
Channel: Baeldung
Viewing all articles
Browse latest Browse all 4535

Retrieve Fields from a Java Class Using Reflection

$
0
0

1. Overview

Reflection is the ability for computer software to inspect its structure at runtime. In Java, we achieve this by using the Java Reflection API. It allows us to inspect the elements of a class such as fields, methods or even inner classes, all at runtime.

This tutorial will focus on how to retrieve the fields of a Java class, including private and inherited fields.

2. Retrieving Fields from a Class

Let’s first have a look at how to retrieve the fields of a class, regardless of their visibility. Later on, we’ll see how to get inherited fields as well.

Let’s start with an example of a Person class with two String fields: lastName and firstName. The former is protected (that’ll be useful later) while the latter is private:

public class Person {
    protected String lastName;
    private String firstName;
}

We want to get both lastName and firstName fields using reflection. 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))
        );
    }

}

As we can see, we get the two fields of the Person class. We check their names and types which matches the fields definitions in the Person class.

3. Retrieving Inherited Fields

Let’s now see how to get the inherited fields of a Java class.

To illustrate this, let’s create a second class named Employee extending Person, with a field of its own:

public class Employee extends Person {
    public int employeeId;
}

3.1. Retrieving Inherited Fields on a Simple Class Hierarchy

Using Employee.class.getDeclaredFields() would only return the employeeId field, as this method doesn’t return the fields declared in superclasses. To also get inherited fields we must also get the fields of the Person superclass.

Of course, we could use the getDeclaredFields() method on both Person and Employee classes and merge their results into a single array. But what if we don’t want to explicitly specify the superclass?

In this case, we can make use of another method of the Java Reflection APIClass::getSuperclass. This gives us the superclass of another class, without us needing to know what that superclass is.

Let’s gather the results of getDeclaredFields() on Employee.class and Employee.class.getSuperclass() and merge them into a single array:

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

We can see here that we’ve gathered the two fields of Person as well as the single field of Employee.

But, is the private field of Person really an inherited field? Not so much. That would be the same for a package-private field. Only public and protected fields are considered inherited.

3.2. Filtering public and protected Fields

Unfortunately, no method in the Java API allows us to gather public and protected fields from a class and its superclasses. The Class::getFields method approaches our goal as it returns all public fields of a class and its superclasses, but not the protected ones.

The only way we have to get only inherited fields is to use the getDeclaredFields() method, as we just did, and filter its results using the Field::getModifiers method. 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.

For example, public is 2^0 and static is 2^3. Therefore calling the getModifiers() method on a public and static field would return 9.

Then, it’s possible to perform a bitwise and between this value and the value of a specific modifier to see if that field has that modifier. If the operation returns something else than 0 then the modifier is applied, otherwise not.

We’re lucky as Java provides us with a utility class to check if modifiers are present in the value returned by getModifiers(). Let’s use the isPublic() and isProtected() methods to gather only inherited fields in our example:

List<Field> 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))
);

As we can see, the result doesn’t carry the private field anymore.

3.3. Retrieving Inherited Fields on a Deep Class Hierarchy

In the above example, we worked on a single class hierarchy. What do we do now if we have a deeper class hierarchy and want to gather all the inherited fields?

Let’s assume we have a subclass of Employee or a superclass of Person – then obtaining the fields of the whole hierarchy will require to check all the superclasses.

We can achieve that by creating a utility method that runs through the hierarchy, building the complete result for us:

List<Field> getAllFields(Class clazz) {
    if (clazz == null) {
        return Collections.emptyList();
    }

    List<Field> result = new ArrayList<>(getAllFields(clazz.getSuperclass()));
    List<Field> filteredFields = Arrays.stream(clazz.getDeclaredFields())
      .filter(f -> Modifier.isPublic(f.getModifiers()) || Modifier.isProtected(f.getModifiers()))
      .collect(Collectors.toList());
    result.addAll(filteredFields);
    return result;
}

This recursive method will search public and protected fields through the class hierarchy and returns all that have been found in a List.

Let’s illustrate it with a little test on a new MonthEmployee class, extending the Employee one:

public class MonthEmployee extends Employee {
    protected double reward;
}

This class defines a new field – reward. Given all the hierarchy class, our method should give us the following fields definitions: Person::lastName, Employee::employeeId and MonthEmployee::reward.

Let’s call the getAllFields() method on MonthEmployee:

@Test
public void givenMonthEmployeeClass_whenGetAllFields_thenThreeFields() {
    List<Field> 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))
    );
}

As expected, we gather all the public and protected fields.

4. Conclusion

In this article, we saw how to retrieve the fields of a Java class using the Java Reflection API.

We first learned how to retrieve the declared fields of a class. After that, we saw how to retrieve its superclass fields as well. Then, we learned to filter out non-public and non-protected fields.

Finally, we saw how to apply all of this to gather the inherited fields of a multiple class hierarchy.

As usual, the full code for this article is available over on our GitHub.


Viewing all articles
Browse latest Browse all 4535

Trending Articles