1. Общ преглед
Отражението е способността на компютърния софтуер да проверява структурата си по време на изпълнение. В Java постигаме това с помощта на API за отразяване на Java . Позволява ни да проверяваме елементите на клас като полета, методи или дори вътрешни класове, всички по време на изпълнение.
Този урок ще се съсредоточи върху извличането на полетата на Java клас, включително частни и наследени полета.
2. Извличане на полета от клас
Нека първо разгледаме как да извлечем полетата на клас, независимо от тяхната видимост. По-късно ще видим как да получим и наследени полета.
Нека започнем с пример за клас Person с две полета String : lastName и firstName . Първият е защитен (това ще бъде полезно по-късно), докато вторият е частен:
public class Person { protected String lastName; private String firstName; }
Искаме да получим полета lastName и 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 областта , тъй като този метод не се връща областта, обявени в superclasses. За да получим също наследени полета, трябва да получим и полетата от суперкласа Person .
Разбира се, бихме могли да използваме метода getDeclaredFields () както за класовете Person, така и за служителите и да обединим резултатите им в един масив. Но какво, ако не искаме да посочим изрично суперкласа?
В този случай можем да използваме друг метод на 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 наистина е наследствено поле? Не толкова. Това би било същото за полето private-private . Само публични и защитени полета се считат за наследени.
3.2. Филтриране на публични и защитени полета
За съжаление никой метод в Java API не ни позволява да събираме публични и защитени полета от клас и неговите суперкласове. Методът Class :: getFields се доближава до нашата цел, тъй като връща всички публични полета на клас и неговите суперкласове, но не и защитените .
Единственият начин, по който трябва да получим само наследени полета, е да използваме метода getDeclaredFields () , както току-що направихме, и да филтрираме резултатите му, използвайки метода Field :: getModifiers . Това връща int, представляващ модификаторите на текущото поле. На всеки възможен модификатор се присвоява мощност от две между 2 ^ 0 и 2 ^ 7 .
Например, public е 2 ^ 0, а static е 2 ^ 3 . Следователно извикването на метода getModifiers () в публично и статично поле ще върне 9.
След това е възможно да се изпълни побитово и между тази стойност и стойността на определен модификатор, за да се види дали това поле има този модификатор. Ако операцията връща нещо различно от 0, тогава модификаторът се прилага, в противен случай не.
Имаме късмет, тъй като Java ни предоставя клас на помощна програма, за да проверим дали в стойността, върната от getModifiers (), има модификатори . Нека използваме методите isPublic () и isProtected () , за да съберем само наследени полета в нашия пример:
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)) );
Както виждаме, резултатът вече не носи частното поле.
3.3. Извличане на наследствени полета в дълбока йерархия на класа
В горния пример работихме по йерархия на един клас. Какво правим сега, ако имаме по-дълбока йерархия на класа и искаме да съберем всички наследени полета?
Да приемем, че имаме подклас на служител или суперклас на човек - тогава получаването на полетата на цялата йерархия ще изисква да проверите всички суперкласове.
Можем да постигнем това, като създадем полезен метод, който преминава през йерархията, изграждайки пълния резултат за нас:
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; }
Този рекурсивен метод ще търси публични и защитени полета чрез йерархията на класа и връща всички, които са намерени в Списък .
Нека го илюстрираме с малък тест за нов клас MonthEfficiee , разширяващ този на Employee :
public class MonthEmployee extends Employee { protected double reward; }
Този клас определя ново поле - награда . Като се има предвид всички йерархични класове, нашият метод трябва да ни даде следните дефиниции на полета : Person :: lastName, Employee :: workerId и MonthEfficiee :: reward .
Нека извикаме метода getAllFields () на MonthEfficiee :
@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)) ); }
Както се очаква, събираме всички обществени и защитени полета.
4. Заключение
В тази статия видяхме как да извлечем полетата на клас Java с помощта на API за отразяване на Java .
Първо научихме как да извлечем декларираните полета на клас. След това видяхме как да извлечем и полетата му от суперклас. След това, ние научихме за филтриране на не- публично и не- защитени области.
И накрая, видяхме как да приложим всичко това, за да съберем наследените полета на йерархия на множество класове.
Както обикновено, пълният код за тази статия е достъпен в нашия GitHub.