Автор оригинала: 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() { ListallFields = 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 .