Перейти к основному содержимому

Извлеките поля из класса Java, используя отражение

· 5 мин. чтения

1. Обзор

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

В этом руководстве основное внимание будет уделено тому, как получить поля класса Java, включая частные и унаследованные поля.

2. Получение полей из класса

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

Начнем с примера класса Person с двумя строковыми полями: lastName и firstName . Первый защищен (это пригодится позже), а второй — приватный:

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

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

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 , расширяющий 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() для 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 является унаследованным полем? Не так много. Это было бы то же самое для частного поля пакета . Только общедоступные и защищенные поля считаются унаследованными.

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

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

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

Например, public — это 2^0 , а static2^3 . Поэтому вызов метода getModifiers() для общедоступного и статического поля вернет 9.

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

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

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

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

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

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

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

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

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

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

Давайте проиллюстрируем это небольшим тестом нового класса MonthEmployee , расширяющего класс Employee :

public class MonthEmployee extends Employee {
protected double reward;
}

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

Давайте вызовем метод getAllFields() для 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))
);
}

Как и положено, собираем все публичные и защищенные поля.

4. Вывод

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

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

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

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