Abrufen von Feldern aus einer Java-Klasse mithilfe von Reflection

1. Übersicht

Reflexion ist die Fähigkeit von Computersoftware, ihre Struktur zur Laufzeit zu überprüfen. In Java erreichen wir dies mithilfe der Java Reflection-API . Es ermöglicht uns, die Elemente einer Klasse wie Felder, Methoden oder sogar innere Klassen zur Laufzeit zu untersuchen.

Dieses Tutorial konzentriert sich auf das Abrufen der Felder einer Java-Klasse, einschließlich privater und geerbter Felder.

2. Felder aus einer Klasse abrufen

Schauen wir uns zunächst an, wie die Felder einer Klasse unabhängig von ihrer Sichtbarkeit abgerufen werden. Später werden wir sehen, wie Sie auch geerbte Felder erhalten.

Beginnen sie mit einem Beispiel einer Start Person - Klasse mit zwei String Feldern: Nachnamen und Vornamen . Ersteres ist geschützt (das wird später nützlich sein), während letzteres privat ist:

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

Wir möchten sowohl Nachname- als auch Vorname- Felder mithilfe von Reflection abrufen . Dies erreichen wir mit der Class :: getDeclaredFields- Methode. Wie der Name schon sagt, werden alle deklarierten Felder einer Klasse in Form eines Field- Arrays zurückgegeben:

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

Wie wir sehen können, erhalten wir die beiden Felder der Person- Klasse. Wir überprüfen ihre Namen und Typen, die mit den Felddefinitionen in der Person- Klasse übereinstimmen .

3. Vererbte Felder abrufen

Lassen Sie uns nun sehen, wie die geerbten Felder einer Java-Klasse abgerufen werden.

Um dies zu veranschaulichen, erstellen wir eine zweite Klasse mit dem Namen " Mitarbeiter erweiternde Person" mit einem eigenen Feld:

public class Employee extends Person { public int employeeId; }

3.1. Vererbte Felder in einer einfachen Klassenhierarchie abrufen

Bei Verwendung von Employee.class.getDeclaredFields () wird nur das Feld employeeId zurückgegeben , da diese Methode die in Superklassen deklarierten Felder nicht zurückgibt. Um auch geerbte Felder zu erhalten, müssen wir auch die Felder der Personen- Oberklasse erhalten.

Natürlich können wir die Methode getDeclaredFields () sowohl für Personen- als auch für Mitarbeiterklassen verwenden und deren Ergebnisse in einem einzigen Array zusammenführen. Aber was ist, wenn wir die Oberklasse nicht explizit angeben wollen?

In diesem Fall können wir eine andere Methode der Java Reflection API verwenden : Class :: getSuperclass . Dies gibt uns die Oberklasse einer anderen Klasse, ohne dass wir wissen müssen, was diese Oberklasse ist.

Sammeln wir die Ergebnisse von getDeclaredFields () in Employee.class und Employee.class.getSuperclass () und führen sie zu einem einzigen Array zusammen:

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

Wir können hier sehen, dass wir die beiden Felder Person sowie das einzelne Feld Mitarbeiter gesammelt haben .

Aber ist das private Feld der Person wirklich ein geerbtes Feld? Nicht so viel. Das wäre das gleiche für ein paketprivates Feld. Nur öffentliche und geschützte Felder gelten als geerbt.

3.2. Filtern von öffentlichen und geschützten Feldern

Leider erlaubt uns keine Methode in der Java-API, öffentliche und geschützte Felder aus einer Klasse und ihren Oberklassen zu sammeln . Die Class :: getFields- Methode nähert sich unserem Ziel, da sie alle öffentlichen Felder einer Klasse und ihrer Oberklassen zurückgibt , jedoch nicht die geschützten .

Die einzige Möglichkeit, nur geerbte Felder abzurufen , besteht darin, wie bisher die Methode getDeclaredFields () zu verwenden und die Ergebnisse mit der Methode Field :: getModifiers zu filtern . Dieser gibt ein int zurück, das die Modifikatoren des aktuellen Feldes darstellt. Jedem möglichen Modifikator wird eine Zweierpotenz zwischen 2 ^ 0 und 2 ^ 7 zugewiesen .

Zum Beispiel öffentlich ist 2 ^ 0 und statisch ist 2 ^ 3 . Daher würde der Aufruf der Methode getModifiers () für ein öffentliches und statisches Feld 9 zurückgeben.

Dann ist es möglich, eine bitweise Ausführung zwischen diesem Wert und dem Wert eines bestimmten Modifikators durchzuführen, um festzustellen, ob dieses Feld diesen Modifikator enthält. Wenn die Operation etwas anderes als 0 zurückgibt, wird der Modifikator angewendet, andernfalls nicht.

Wir haben Glück, dass Java uns eine Utility-Klasse zur Verfügung stellt, mit der überprüft werden kann, ob Modifikatoren in dem von getModifiers () zurückgegebenen Wert vorhanden sind . Verwenden wir die Methoden isPublic () und isProtected () , um in unserem Beispiel nur geerbte Felder zu erfassen :

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

Wie wir sehen können, enthält das Ergebnis nicht mehr das private Feld.

3.3. Vererbte Felder in einer tiefen Klassenhierarchie abrufen

Im obigen Beispiel haben wir an einer einzelnen Klassenhierarchie gearbeitet. Was machen wir jetzt, wenn wir eine tiefere Klassenhierarchie haben und alle geerbten Felder sammeln möchten?

Nehmen wir an, wir haben eine Unterklasse von Mitarbeitern oder eine Oberklasse von Personen. Um die Felder der gesamten Hierarchie zu erhalten, müssen alle Oberklassen überprüft werden.

Wir können dies erreichen, indem wir eine Dienstprogrammmethode erstellen, die die Hierarchie durchläuft und das vollständige Ergebnis für uns erstellt:

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

Diese rekursive Methode durchsucht öffentliche und geschützte Felder durch die Klassenhierarchie und gibt alle in einer Liste gefundenen zurück .

Lassen Sie es uns mit einem kleinen Test an einer neuen MonthEmployee- Klasse veranschaulichen , der die Employee- Klasse erweitert :

public class MonthEmployee extends Employee { protected double reward; }

Diese Klasse definiert eine neue Feldbelohnung . Bei allen Hierarchieklassen sollte unsere Methode die folgenden Felddefinitionen enthalten: Person :: lastName, Employee :: employeeId und MonthEmployee :: Belohnung .

Rufen wir die Methode getAllFields () in MonthEmployee auf :

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

Wie erwartet sammeln wir alle öffentlichen und geschützten Felder.

4. Fazit

In diesem Artikel haben wir gesehen, wie die Felder einer Java-Klasse mithilfe der Java Reflection-API abgerufen werden .

Wir haben zuerst gelernt, wie man die deklarierten Felder einer Klasse abruft. Danach haben wir gesehen, wie man auch seine Superklassenfelder abruft. Dann haben wir gelernt, nicht öffentliche und nicht geschützte Felder herauszufiltern .

Schließlich haben wir gesehen, wie all dies angewendet werden kann, um die geerbten Felder einer Hierarchie mit mehreren Klassen zu erfassen.

Der vollständige Code für diesen Artikel ist wie üblich auf unserem GitHub verfügbar.