Рубрики
Без рубрики

Получение полей из класса Java с помощью отражения

Узнайте, как получить поля класса с помощью отражения, включая унаследованные поля

Автор оригинала: François Dupire.

1. Обзор

Отражение – это возможность компьютерного программного обеспечения проверять свою структуру во время выполнения. В Java мы достигаем этого с помощью Java Reflection API . Это позволяет нам проверять элементы класса, такие как поля, методы или даже внутренние классы, все во время выполнения.

Этот учебник будет посвящен извлечению полей класса Java, включая частные и унаследованные поля.

2. Извлечение полей из класса

Давайте сначала рассмотрим, как извлекать поля класса, независимо от их видимости. Позже мы увидим, как получить унаследованные поля.

Давайте начнем с примера класса Person с двумя полями String : Фамилия и Имя . Первый является защищенным (это будет полезно позже), в то время как второй является частным:

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

Мы хотим получить оба поля Фамилия и Имя с помощью отражения. Мы достигнем этого с помощью метода Class::getDeclaredFields . Как следует из его названия, он возвращает все объявленные поля класса в виде массива Field :

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

}

Как мы видим, мы получаем два поля класса Person . Мы проверяем их имена и типы, которые соответствуют определениям полей в классе Person .

3. Получение Унаследованных Полей

Теперь давайте посмотрим, как получить унаследованные поля класса Java.

Чтобы проиллюстрировать это, давайте создадим второй класс с именем Employee extending Person , с собственным полем:

public class Employee extends Person {
    public int employeeId;
}

3.1. Получение унаследованных полей в простой иерархии классов

Использование Employee.class.getDeclaredFields() вернет только EmployeeID поле , так как этот метод не возвращает поля, объявленные в суперклассах. Чтобы также получить унаследованные поля, мы также должны получить поля суперкласса Person .

Конечно, мы могли бы использовать метод getDeclaredFields() для классов Person и Employee и объединить их результаты в один массив. Но что, если мы не хотим явно указывать суперкласс?

В этом случае мы можем использовать другой метод Java Reflection API : Class::getSuperclass . Это дает нам суперкласс другого класса, без необходимости знать, что это за суперкласс.

Давайте соберем результаты getDeclaredFields() on Employee.class и Employee.class.getSuperclass() и объединить их в один массив:

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

Здесь мы видим, что мы собрали два поля Person , а также одно поле Employee .

Но действительно ли частное поле Person является унаследованным полем? Не так уж и много. Это было бы то же самое для поля package-private . Только общедоступные и защищенные поля считаются унаследованными.

3.2. Фильтрация открытых и защищенных полей

К сожалению, ни один метод в Java API не позволяет нам собирать public и protected поля из класса и его суперклассов. Метод Class::GetFields приближается к нашей цели, поскольку он возвращает все общедоступные поля класса и его суперклассов, но не защищенные .

Единственный способ получить только унаследованные поля-это использовать метод getDeclaredFields () , как мы только что сделали, и фильтровать его результаты с помощью метода Field::getModifiers . Этот возвращает int , представляющий модификаторы текущего поля. Каждому возможному модификатору присваивается степень два между 2^0 и 2^7 .

Например, public is 2^0 и static is 2^3 . Поэтому вызов метода getModifiers() в поле public и static вернет 9.

Затем можно выполнить побитовое и между этим значением и значением определенного модификатора, чтобы увидеть, есть ли в этом поле этот модификатор. Если операция возвращает что-то другое, чем 0, то модификатор применяется, в противном случае нет.

Нам повезло, так как Java предоставляет нам служебный класс для проверки наличия модификаторов в значении, возвращаемом getModifiers() . Давайте использовать методы is Public() и is Protected() для сбора только унаследованных полей в нашем примере:

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

Как мы видим, результат больше не содержит поля private .

3.3. Получение унаследованных полей в Глубокой иерархии классов

В приведенном выше примере мы работали над единой иерархией классов. Что нам теперь делать, если у нас более глубокая иерархия классов и мы хотим собрать все унаследованные поля?

Предположим, у нас есть подкласс Employee или суперкласс Person– , тогда для получения полей всей иерархии потребуется проверить все суперклассы.

Мы можем достичь этого, создав служебный метод, который проходит через иерархию, создавая для нас полный результат:

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

Этот рекурсивный метод будет искать общественный и защищенный поля через иерархию классов и возвращает все, что было найдено в Список .

Давайте проиллюстрируем это небольшим тестом на новом Месячном сотруднике классе, расширяя Сотрудника :

public class MonthEmployee extends Employee {
    protected double reward;
}

Этот класс определяет новое поле – вознаграждение . Учитывая весь класс иерархии, наш метод должен дать нам следующие поля определения: Person::Фамилия, Employee::EmployeeID и MonthEmployee::вознаграждение .

Давайте вызовем метод getAllFields() на сотруднике месяца :

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

Как и ожидалось, мы собираем все поля public и protected .

4. Заключение

В этой статье мы рассмотрели, как получить поля класса Java с помощью Java Reflection API .

Сначала мы узнали, как извлекать объявленные поля класса. После этого мы увидели, как получить его поля суперкласса. Затем мы научились отфильтровывать не общедоступные и не защищенные поля.

Наконец, мы увидели, как применить все это для сбора унаследованных полей иерархии нескольких классов.

Как обычно, полный код этой статьи доступен на нашем GitHub .