Автор оригинала: 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() для сбора только унаследованных полей в нашем примере:
ListpersonFields = 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– , тогда для получения полей всей иерархии потребуется проверить все суперклассы.
Мы можем достичь этого, создав служебный метод, который проходит через иерархию, создавая для нас полный результат:
ListgetAllFields(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 .