Ophalen van velden uit een Java-klasse met behulp van reflectie

1. Overzicht

Reflectie is het vermogen van computersoftware om de structuur tijdens runtime te inspecteren. In Java bereiken we dit door de Java Reflection API. Het stelt ons in staat om de elementen van een klasse, zoals velden, methoden of zelfs innerlijke klassen, allemaal tijdens runtime te inspecteren.

Deze tutorial zal zich concentreren op het ophalen van de velden van een Java-klasse, inclusief privé- en overgenomen velden.

2. Velden ophalen uit een klasse

Laten we eerst eens kijken hoe we de velden van een klasse kunnen ophalen, ongeacht hun zichtbaarheid. Later zullen we zien hoe we ook erfelijke velden kunnen krijgen.

Laten we beginnen met een voorbeeld van een Persoon klas met twee Draad velden: achternaam en Voornaam. De eerste is beschermd (dat komt later van pas) terwijl de laatste is privaat:

openbare klasse Persoon {beschermde String lastName; private String voornaam; }

We willen beide krijgen achternaam en Voornaam velden met behulp van reflectie. We zullen dit bereiken door de Klasse :: getDeclaredFields methode. Zoals de naam doet vermoeden, retourneert dit alle verklaard velden van een klasse, in de vorm van een Veld matrix:

openbare klasse PersonAndEmployeeReflectionUnitTest {/ * ... constanten ... * / @Test openbare leegte gegevenPersonClass_whenGetDeclaredFields_thenTwoFields () {Veld [] 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))); }}

Zoals we kunnen zien, krijgen we de twee velden van de Persoon klasse. We controleren hun namen en typen die overeenkomen met de velddefinities in het Persoon klasse.

3. Overgenomen velden ophalen

Laten we nu kijken hoe we de overgeërfde velden van een Java-klasse kunnen krijgen.

Om dit te illustreren, maken we een tweede klasse met de naam Werknemer uitbreiden Persoon, met een eigen veld:

public class Employee verlengt Persoon {public int employeeId; }

3.1. Overgenomen velden ophalen in een eenvoudige klassehiërarchie

Gebruik makend van Employee.class.getDeclaredFields () zou alleen de medewerker-ID veld-, aangezien deze methode niet de velden retourneert die in superklassen zijn gedeclareerd. Om ook overgeërfde velden te krijgen, moeten we ook de velden van de Persoon superklasse.

Natuurlijk kunnen we de getDeclaredFields () methode op beide Persoon en Werknemer klassen en hun resultaten samenvoegen in een enkele array. Maar wat als we de superklasse niet expliciet willen specificeren?

In dit geval, we kunnen gebruik maken van een andere methode van de Java Reflection API: Klasse :: getSuperclass. Dit geeft ons de superklasse van een andere klasse, zonder dat we hoeven te weten wat die superklasse is.

Laten we de resultaten verzamelen van getDeclaredFields () Aan Werknemer. Klasse en Employee.class.getSuperclass () en voeg ze samen in een enkele array:

@Test openbare leegte gegevenEmployeeClass_whenGetDeclaredFieldsOnBothClasses_thenThreeFields () {Veld [] personFields = Employee.class.getSuperclass (). GetDeclaredFields (); Veld [] employeeFields = Employee.class.getDeclaredFields (); Veld [] allFields = nieuw veld [employeeFields.length + personFields.length]; Arrays.setAll (allFields, i -> (i <personFields.length? PersonFields [i]: employeeFields [i - personFields.length])); assertEquals (3, allFields.length); Veld lastNameField = allFields [0]; assertEquals (LAST_NAME_FIELD, lastNameField.getName ()); assertEquals (String.class, lastNameField.getType ()); Veld firstNameField = allFields [1]; assertEquals (FIRST_NAME_FIELD, firstNameField.getName ()); assertEquals (String.class, firstNameField.getType ()); Veld employeeIdField = allFields [2]; assertEquals (EMPLOYEE_ID_FIELD, employeeIdField.getName ()); assertEquals (int.class, employeeIdField.getType ()); }

We kunnen hier zien dat we de twee velden van hebben verzameld Persoon evenals het enkele veld van Werknemer.

Maar is het privaat gebied van Persoon echt een erfelijk veld? Niet zo veel. Dat zou hetzelfde zijn voor een pakket-privé veld. Enkel en alleen openbaar en beschermd velden worden als overgeërfd beschouwd.

3.2. Filteren openbaar en beschermd Velden

Helaas stelt geen enkele methode in de Java API ons in staat om te verzamelen openbaar en beschermd velden van een klasse en zijn superklassen. De Klasse :: getFields methode benadert ons doel omdat het alles teruggeeft openbaar velden van een klasse en zijn superklassen, maar niet de beschermd degenen.

De enige manier waarop we alleen overgeërfde velden moeten krijgen, is door de getDeclaredFields () methode, zoals we net deden, en filter de resultaten met de Veld :: getModifiers methode. Deze retourneert een int die de modificatoren van het huidige veld vertegenwoordigen. Elke mogelijke modificator krijgt een macht van twee tussen 2^0 en 2^7.

Bijvoorbeeld, openbaar is 2^0 en statisch is 2^3. Daarom bellen met de getModifiers () methode op een openbaar en statisch veld zou 9 teruggeven.

Dan is het mogelijk om een bitsgewijs en tussen deze waarde en de waarde van een specifieke modifier om te zien of dat veld die modifier heeft. Als de bewerking iets anders dan 0 retourneert, wordt de modifier toegepast, anders niet.

We hebben geluk, want Java biedt ons een hulpprogramma-klasse om te controleren of er modifiers aanwezig zijn in de waarde die wordt geretourneerd door getModifiers (). Laten we de isPublic () en isProtected () methoden om alleen overgeërfde velden te verzamelen in ons voorbeeld:

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

Zoals we kunnen zien, draagt ​​het resultaat niet de privaat veld meer.

3.3. Overgenomen velden ophalen in een diepe klassehiërarchie

In het bovenstaande voorbeeld hebben we gewerkt aan een enkele klassehiërarchie. Wat doen we nu als we een diepere klassenhiërarchie hebben en alle overgeërfde velden willen verzamelen?

Laten we aannemen dat we een subklasse hebben van Werknemer of een superklasse van Persoon - om vervolgens de velden van de hele hiërarchie te verkrijgen, moeten alle superklassen worden gecontroleerd.

We kunnen dat bereiken door een hulpprogramma-methode te creëren die door de hiërarchie loopt en het volledige resultaat voor ons opbouwt:

Lijst getAllFields (Class clazz) {if (clazz == null) {return Collections.emptyList (); } Lijstresultaat = nieuwe ArrayList (getAllFields (clazz.getSuperclass ())); Lijst gefilterdeFields = Arrays.stream (clazz.getDeclaredFields ()) .filter (f -> Modifier.isPublic (f.getModifiers ()) || Modifier.isProtected (f.getModifiers ())) .collect (Collectors.toList () ); result.addAll (gefilterdeFields); resultaat teruggeven; }

Deze recursieve methode zal zoeken openbaar en beschermd velden door de klassenhiërarchie en retourneert alles wat is gevonden in een Lijst.

Laten we het illustreren met een kleine test op een nieuw Maand Werknemer klasse, waardoor de Werknemer een:

openbare klasse MonthEmployee verlengt werknemer {beschermde dubbele beloning; }

Deze klasse definieert een nieuw veld - beloning. Gezien alle hiërarchieklassen, zou onze methode ons de volgende velden moeten geven definities: Persoon :: achternaam, Werknemer :: medewerkerId en Maand Werknemer :: beloning.

Laten we de getAllFields () methode op Maand Werknemer:

@Test openbare leegte gegevenMonthEmployeeClass_whenGetAllFields_thenThreeFields () {Lijst 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 (). is gelijk aan (MONTH_EMPLOYEE_REWARD_FIELD) && field.getType (). equals (double.class))); }

Zoals verwacht verzamelen we alle openbaar en beschermd velden.

4. Conclusie

In dit artikel hebben we gezien hoe we de velden van een Java-klasse kunnen ophalen met de Java Reflection API.

We hebben eerst geleerd hoe we de gedeclareerde velden van een klasse kunnen ophalen. Daarna hebben we gezien hoe we ook de superklasse-velden konden ophalen. Vervolgens leerden we om niet-openbaar en niet-beschermd velden.

Ten slotte hebben we gezien hoe we dit allemaal konden toepassen om de overgeërfde velden van een hiërarchie met meerdere klassen te verzamelen.

Zoals gewoonlijk is de volledige code voor dit artikel beschikbaar op onze GitHub.