Java Reflection

Java bietet durch den Einsatz von Reflection eine Möglichkeit, die zur Kompilierzeit noch unbekannten Metadateninformationen über Klassen und Objekte zur Laufzeit abzurufen. Reflection findet oft in (Java) Frameworks zum Testen der Anwendung (z.B. JUnit 3.x) Verwendung. Auch Debugger können dank Reflection zur Laufzeit Informationen über Objekte ausgeben, die andernfalls nur mit sehr viel Aufwand lesbar wären.
Die Reflection API ermöglicht es sogar, auf geschützte (private, protected, package private) Attribute und Methoden zuzugreifen. Diese Möglichkeit sollte jedoch möglichst vermieden werden, da andernfalls das laufende System durch Instabilität oder sogar zum Absturz gebracht werden kann. Grundsätzlich sollte Reflection auch nur dann verwendet werden, wenn es keine andere Alternative gibt.

Wichtige Metadatenklassen

Die wohl wichtigsten Informationen über die Klassen welche Metainformationen enthalten einmal kurz zusammengefasst:

java.lang.Class
Enthält Metainformationen über die Klasse. Zum Beispiel: Annotationinformationen, Konstruktorinformationen, Methoden, Attribute, Interfaces, ENUM Konstanten und und und.

java.lang.reflect.Field
Die Field Objekte enthalten beispielhaft Metainformationen über den Attributnamen, Typen oder die Sichtbarkeit. Abgerufen werden können diese Informationen unter anderem über die Methode getFields() des Class Objektes.

java.lang.reflect.Method
Die Method Objekte können beispielsweise über die Methode getMethods() des Class Objektes ausgelesen werden. Sie beinhalten Informationen über die Methode wie den Namen, die Parameter, den Rückgabewert, die Sichtbarkeit und einige Weitere.

Class Reflection

Im Allgemeinen gibt es drei verschiedene Möglichkeiten an das Class-Objekt (Metadaten) zu gelangen. Hierzu jeweils ein Beispiel:

		java.lang.String value = "reflection example";

		Class<?> option1 = value.getClass();
		Class<?> option2 = Class.forName("java.lang.String");
		Class<?> option3 = String.class;

		// Ergebnis: class java.lang.String
		System.out.println(option1.toString());
		// Ergebnis: class java.lang.String	
		System.out.println(option2.toString());
		// Ergebnis: class java.lang.String
		System.out.println(option3.toString());

Im ersten Fall (option1) hat der Classloader die Klasse bereits geladen und ein verwendbares Objekt liegt vor. Über getClass() kann hier auf das Class-Objekt zugegriffen werden.
Die getClass() Methode ist im übrigen im jedem Java Objekt vorhanden.

Fall zwei (option2) zeigt den Zugriff auf das Class-Objekt über die statische Methode forName(). Übergeben wird der vollqualifizierte Klassenname (Vorsicht vor Tippfehlern!). Die Variante kann verwendet werden, wenn der konkrete Typ zur Kompilierzeit noch nicht bekannt ist.

Im letzten Fall (option3) wird davon ausgegangen das die Klasse vor dem Zeitpunkt der Kompilierung zur Verfügung steht. Das statische Attribut .class liefert unter dieser Voraussetzung das Class-Objekt und somit alle Klasseninformationen.

Paketinformationen

Über dass Class Objekt können Informationen über die Paketstruktur geladen werden.

option1.getPackage()

Superklasse

Oder aber auch über die Superklassen. Bei POJO’s wird dies immer Object sein.

option1.getSuperclass()

Interfaces

Falls vorhanden, können auch die Interfaces ausgelesen werden.

option1.getInterfaces()

Klassenname

Und sogar der Klassenname wird nicht verborgen …

option1.getName()

Modifier

Über getModifies() wird eine Liste mit int Werten zurückgeliefert. Mit Hilfe der java.lang.reflect.Modifier Klasse können über statische Methoden wie isPublic(), isStatic(), isSynchronized() die int Werte genau identifiziert werden.

option1.getModifiers()

Konstruktor

Da eine Klasse durchaus aus mehreren Konstruktoren besteht, liefert getConstructors() ebenfalls eine Liste mit Konstruktor Objekten zurück.

getConstructors()

Annotations

Annotations werden mit getAnnotations() ausgelesen.

option1.getAnnotations()

Methoden

Alle öffentlichen Methoden können über getMethods() gelesen werden.

option1.getMethods()

Fields

Die öffentlichen Attribute liefert getFields().

option1.getFields()

Method Reflection

Nachdem nun bekannt ist, wie die Metainformationen aus einer Klasse gelesen werden können, hierzu ein kleines Beispiel anhand eines Methodenaufrufs einer Klasse.

			// Ein Objekt der Klasse Person über den vollqualifizierten Namen erstellen
			Person person = (Person) Class.forName("de.itblogging.reflection.Person").newInstance();
			// Das Class Objekt über getClass() laden
			Class<?> clazz = person.getClass();

			// Methodeaufruf definieren [Methodenname, Parametertyp1, Parametertyp2]
			Method method = clazz.getMethod("callMyName", String.class, String.class);
			// Methode aufrufen
			method.invoke(person, "Simon", "Michel");

Wie auf das Class Objekt bei einem vorhandenen Objekt zugegriffen werden konnte, wurde zuvor bereits geklärt. Die Methode getMethod() erwartet mindestens einen Parameter – den Methodennamen. Als zweiten, dritten, vierten … Parameter müssen anschließend die konkreten Parametertypen in richtiger Reihenfolge übergeben werden. Ist das Method Objekt erstellt, ruft die Methode invoke() die zuvor definierte Methode auf. In diesem Fall werden zusätzlich die zwei String Parameter übergeben.
Die öffentliche Methode callMyName() macht nichts anderes, als dass diese den übergebenen Namen in die Konsole schreibt. Den Aufwand welcher bei Reflection allein durch das Exceptionhandling betrieben werden muss, habe ich in diesem Beispiel bewusst wegfallen lassen. Bearbeitet werden müssten hier sechs verschiebene Exceptions!
Eine einzelne Methode kann also über getMethod() angesprochen werden. Um alle öffentlichen in der Klasse deklarierten Methoden auflisten zu lassen, hilft die Methode getMethods().

Zugriff auf private Attribute und Methoden

Unter der Voraussetzung das eine Klassenmethode öffentlich ist, genügt getMethod() um auf sie zuzugreifen. Das selbe Verhalten gilt für getField(). In Ausnahmefällen soll es aber dann vielleicht möglich sein, nicht nur auf die öffentlichen Methoden und Attribute zuzugreifen, sondern auch auf die geschützten, die privaten Methoden.

Private Attribute

Vorausgesetzt die Klasse Person würde ein privates Attribut namens password enthalten, so müsste im ersten Schritt die Methode über getDeclaredField() geladen werden. Anschließend wird die Zugriffseinschränkung über setAccessible(true) ausgehebelt. Als letztes muss dann nur noch der Wert neu gesetzt werden. Fertig.
Übrigens: Bei statischen Methoden wird statt der Objektreferenz der Wert null übergeben.

		Person person = new Person();
		Class<?> option1 = person.getClass();
		
		// privates Attribut "helloWorld" laden
		Field password = option1.getDeclaredField("password");
		// Zugriffseinschränkung aufheben
		password.setAccessible(true);
		// Attribut neu setzen
		password.set(person, "weltmeister");
		// Ausgabe des neuen Wertes.
		System.out.println(password.get(person));

Private Methoden

Bei den privaten Methoden ist die Vorgehensweise nahezu die selbe wie bei den private Attributen.

		Person person = new Person();
		Class<?> option1 = person.getClass();
		
		// Methode laden
		Method method = option1.getDeclaredMethod("getPassword");
		// Zugriffseinschränkung aufheben
		method.setAccessible(true);
		// Methode ausführen und den Rückgabewert speichern
		String personPassword = (String) method.invoke(person);
		// Ausgabe des Passworts
		System.out.println(personPassword);

In diesem Codebeispiel wird die private Methode getPassword() aufgerufen und anschließend das Ergebnis in die Konsole geschrieben.