17.5Attribute, Methoden und Konstruktoren
Ein Class-Objekt bietet nicht nur Zugriff auf Oberklassen, Sichtbarkeiten, Modifizierer und Schnittstellen, sondern natürlich auch auf die Variablen, Methoden und Konstruktoren einer Klasse oder Schnittstelle. Daher kooperiert Class mit fünf weiteren Typen:
Field: Ermöglicht den Zugriff auf die Objekt- und Klassenvariablen, um später Belegungen lesen und Werte verändern zu können.
Method: Steht für die Methoden einer Klasse bzw. Operationen der Schnittstellen. So liefert die Class-Methode getDeclaredMethods() die Methoden, die dann später mit invoke(Object obj, Object... args) aufgerufen werden können.
Constructor: Steht für die Konstruktoren einer Klasse. So gibt zum Beispiel die Class-Methode getConstructors() ein Feld von Konstruktoren zurück.
Annotation: Repräsentiert die Annotationen, die an der Klasse/Schnittstelle festgemacht sind. So liefert zum Beispiel die Class-Methode getAnnotations() die festgemachten Annotationen.
Package: getPackage() liefert ein Package-Objekt für die Klasse, die eine Versionsnummer beinhaltet, wenn diese im Manifest gesetzt wurde.
Weiterhin gibt es folgende allgemeine Typbeziehungen:
Constructor und Method erweitern (ab Java 8) die abstrakte Oberklasse Executable – sie fasst Methoden für codeausführende Reflection-Klassen zusammen.
Die Klassen Class, Method, Field und Constructor implementieren eine Schnittstelle Member, um etwa den Namen, die Modifizierer oder die deklarierende Klasse zu erfragen.
Die Klassen Class und Executable (und somit indirekt Constructor und Method) implementieren die Schnittstelle GenericDeclaration, da sie generische Typvariablen deklarieren können.
Die Klassen Field und Executable (und damit indirekt Constructor und Method) erweitern AccessibleObject, um die Sichtbarkeit zu kontrollieren. Per Reflection ist im Prinzip alles offen (wenn auch nicht standardmäßig), auch private Attribute oder Methoden.
Diverse Java-Typen können annotiert werden, was die Reflection-Klassen durch die Implementierung der Schnittstelle AnnotatedElement ausdrücken. Es implementieren Class, AccessibleObject (damit indirekt Field, Constructor, Method), Executable (und damit wieder indirekt Constructor, Method), Package und Parameter (neu in Java 8) die Schnittstelle AnnotatedElement. Von der Schnittstelle gibt es diverse Unterschnittstellen (einige neu in Java 8): AnnotatedArrayType, AnnotatedParameterizedType, AnnotatedType, AnnotatedTypeVariable, AnnotatedWildcardType, GenericDeclaration, TypeVariable<D>.
Reflections-Exceptions und ReflectiveOperationException
Ist etwas so dynamisch wie Reflection, kann eine Menge schiefgehen. Nahezu alle Methoden zum Zugriff auf Laufzeitinformationen lösen daher die eine oder andere Ausnahme aus. An dieser Stelle sollen die zentralen Ausnahmen kurz vorgestellt werden. Alle stammen aus dem Paket java.lang:
NoSuchFieldException und NoSuchMethodException: Das Attribut oder die Methode wird erfragt, aber existiert nicht.
ClassNotFoundException: Der Klassenlader versucht, die Klasse zu laden, konnte sie aber nicht bekommen. Wird ausgelöst etwa von Class.forName(String).
InstantiationException: Der Versuch, ein Exemplar aufzubauen, scheitert, etwa wenn versucht wird, eine abstrakte Klasse zu instanziieren oder den Standard-Konstruktor aufzurufen, die Klasse aber nur parametrisierte Konstruktoren deklariert.
IllegalAccessException: Die Sichtbarkeit ist zum Beispiel private, sodass von außen ein Attribut nicht erfragt, eine Methode nicht aufgerufen oder ein Exemplar nicht aufgebaut werden kann.
InvocationTargetException: Eine Methode oder ein Konstruktor können eine Exception auslösen. Die InvocationTargetException packt diese Exception ein.
Abbildung 17.2UML-Diagramm für ReflectiveOperationException
Einige Methoden lösen weniger Ausnahmen im Fehlerfall aus, andere mehr. Die Constructor-Methode newInstance() führt gleich vier Ausnahmen am throws auf. Oftmals führt das zu großen catch-Blöcken mit dupliziertem Code. Um den Programmcode etwas einfacher zu gestalten, gibt es für die sechs Ausnahmen eine Oberklasse ReflectiveOperationException, sodass bei identischer Behandlung alles vom Typ ReflectiveOperationException gecatcht werden kann:
ClassNotFoundException extends ReflectiveOperationException
IllegalAccessException extends ReflectiveOperationException
InstantiationException extends ReflectiveOperationException
InvocationTargetException extends ReflectiveOperationException
NoSuchFieldException extends ReflectiveOperationException
NoSuchMethodException extends ReflectiveOperationException
ReflectiveOperationException selbst ist eine Unterklasse von Exception und nicht von RuntimeException. Sie muss daher explizit behandelt werden, genauso wie die anderen Ausnahmen vorher.
17.5.1Reflections – Gespür für die Attribute einer Klasse
Besonders bei Klassen-Browsern oder GUI-Buildern ist es interessant, auf die Variablen eines Objekts zuzugreifen, das heißt, ihre Werte auszulesen und zu verändern. Damit wir an beschreibende Objekte für die in einer Klasse deklarierten bzw. aus Oberklassen geerbten Variablen gelangen, rufen wir die Methode getFields() für das Class-Objekt der Klasse auf, die uns interessiert. Als Ergebnis erhalten wir ein Array von Field-Objekten. Jeder Array-Eintrag beschreibt eine Objekt- oder Klassenvariable, auf die wir zugreifen dürfen. Nur auf öffentliche, also public-Elemente, haben wir per (gewöhnlicher) Reflection Zugriff (auf eine privilegierte Reflection gehen wir hier nicht ein). Schnittstellen deklarieren ja bekanntlich nur Konstanten. Somit ist der schreibende Zugriff, den wir später näher betrachten wollen, nur auf in Klassen deklarierte Variablen beschränkt. Lesen ist natürlich bei Konstanten und Variablen gleichermaßen erlaubt. Beim Zugriff auf die Attribute mittels getFields() müssen wir aufpassen, dass wir uns keine SecurityException einfangen. Das kann uns aber bei vielen Methoden passieren, und weil SecurityException eine RuntimeException ist, muss sie auch nicht extra aufgefangen werden. In der Dokumentation ist sie daher nicht angegeben.
implements Serializable, GenericDeclaration, Type, AnnotatedElement
Field[] getFields()
Liefert ein Array mit Field-Objekten. Die Einträge sind unsortiert. Das Array hat die Länge 0, wenn die Klasse bzw. Schnittstelle keine öffentlichen Variablen deklariert oder erbt. getFields() liefert automatisch auch Einträge für die aus Oberklassen bzw. Schnittstellen geerbten öffentlichen Variablen.Field getField(String name) throws NoSuchFieldException
Erfragt ein bestimmtes Feld.
Beispiel: Attribute von SimpleDateFormat
Um für SimpleDateFormat alle Objekt- und Klassenvariablen mit ihren Datentypen herauszufinden, lassen wir eine Schleife über das Field-Array laufen. Ein Field-Objekt liefert mit getName() den Namen der Variablen. Den zugehörigen Datentyp bekommen wir mit getType(), was ein Class-Objekt für den Typ liefert, und dann gibt darauf ein getName() eine String-Repräsentation des Typs:
Listing 17.8com/tutego/insel/meta/ShowFields.java, main()
System.out.println( "class " + c.getName() + " {" );
for ( Field publicField : c.getFields() ) {
String fieldName = publicField.getName();
String fieldType = publicField.getType().getName();
System.out.printf( " %s %s;%n", fieldType, fieldName );
}
System.out.println( "}" );
Dies ergibt die (gekürzte) Ausgabe:
int ERA_FIELD;
int YEAR_FIELD;
...
int SHORT;
int DEFAULT;
}
17.5.2Schnittstelle Member für Eigenschaften
Klassen haben Eigenschaften, und zwar Attribute und Methoden bzw. Konstruktoren. In der Reflection-API gibt es dazu eine Schnittstelle Member, die beschreibt, was allen Eigenschaften gegeben ist, etwa der Name. Neben Fieldimplementiert auch Executable das Interface Member, sodass auch die Executable-Unterklassen Method und Constructor die folgenden vier Methoden besitzen:
String getName()
Liefert den Namen der Eigenschaft. Das ist entweder der Name einer Variablen, ein Methodenname oder der Name eines Konstruktors.int getModifiers()
Liefert die Modifizierer für die Eigenschaft, kodiert als Ganzzahl. Siehe dazu auch Abschnitt 17.4.6, »Modifizierer und die Klasse Modifier *«.Class<?> getDeclaringClass()
Liefert das Class-Exemplar für den Typ, in dem die Eigenschaft deklariert wurde.boolean isSynthetic()
Ist die Eigenschaft vom Compiler angelegt, also synthetisch? Der Compiler legt für Generics oftmals so genannte Brückenmethoden an, die sind dann synthetisch.
17.5.3Field-Klasse
Die Klasse Field implementiert die von der Schnittstelle Member vorgeschriebenen Operationen, etwa zum Geben des Variablennamens. Insgesamt bietet ein Field-Objekt damit alles Nötige, um den Namen des Attributs, den Datentyp und auch wieder die deklarierten Modifizierer zu erfragen.
[zB]Beispiel
Laufe in einer Schleife über die Field-Objekte von SimpleDateFormat, und rufe toString() auf jedem Element auf:
System.out.println( field );
Die Ausgabe ist:
public static final int java.text.DateFormat.YEAR_FIELD
…
public static final int java.text.DateFormat.SHORT
public static final int java.text.DateFormat.DEFAULT
Werfen wir einen Blick auf die Implementierung der toString()-Methode der Klasse Field, dann sind einige der Anfragemethoden gut sichtbar:
Listing 17.9java.lang.reflect.Field.java, toString()
int mod = getModifiers();
return (((mod == 0) ? "" : (Modifier.toString(mod) + " "))
+ getType().getTypeName() + " "
+ getDeclaringClass().getTypeName() + "."
+ getName());
}
Die aus Member stammenden Methoden sind nicht noch einmal aufgeführt, und Field hat auch noch mehr Methoden; eine vollständigere Beschreibung folgt später, da die verbleibenden Methoden mit dem Anfragen und Setzen von Variablenbelegungen verbunden sind.
extends AccessibleObject
implements Member
Class<?> getType()
Liefert ein Class-Objekt, das dem Datentyp der Variablen entspricht.String toString()
Liefert eine String-Repräsentation. Am Anfang stehen die Sichtbarkeitsmodifizierer (public, protected oder private), und es folgen die weiteren Modifizierer (static, final, transient, volatile). Dann kommen der Datentyp, gefolgt vom voll qualifizierten Namen der deklarierenden Klasse, und schließlich der Name der Variablen.
17.5.4Methoden einer Klasse erfragen
Um herauszufinden, über welche Methoden eine Klasse verfügt, wenden wir eine ähnliche Vorgehensweise an wie bei den Variablen: getMethods(). Diese Methode liefert ein Array mit Method-Objekten. Über ein Method-Objekt lassen sich Methodenname, Ergebnistyp, Parametertypen, Modifizierer und eventuell resultierende Exceptions erfragen. Wir werden später sehen, dass sich die durch ein Method-Exemplar repräsentierte Methode über invoke(…) aufrufen lässt.
[»]Hinweis
Auch wenn zwei Klassen die gleiche Methode besitzen, muss doch ein Method-Objekt immer für jede Klasse erfragt werden. Method-Objekte sind immer mit dem Class-Objekt verbunden.
implements Serializable, GenericDeclaration, Type, AnnotatedElement
Method[] getMethods()
Gibt ein Array von Method-Objekten zurück, die alle öffentlichen Methoden der Klasse/Schnittstelle beschreiben. Geerbte Methoden werden mit in die Liste übernommen. Die Elemente sind nicht sortiert, noch gibt es keine Reihenfolge. Die Länge des Arrays ist null, wenn es keine öffentlichen Methoden gibt.Method getMethod(String name, Class... parameterTypes)
throws NoSuchMethodException
Liefert zu einem Methodennamen und einer Parameterliste das passende Method-Objekt oder löst eine NoSuchMethodException aus. Besitzt die Methode keine Parameter – wie eine übliche getXXX()-Methode –, ist das Argument null und wird wegen der Varargs auf Class[] angepasst.
Nachdem wir nun mittels getMethods() ein Array von Method-Objekten erhalten haben, lassen die Method-Objekte verschiedene Abfragen zu. So liefert getName() den Namen der Methode, getReturnType() den Ergebnistyp, und getParameterTypes() erzeugt ein Array von Class-Objekten, das die Typen der Methodenparameter widerspiegelt. Wir kennen dies schon von den Attributen.
Wir wollen nun ein Programm betrachten, das alle Methoden und ihre Parametertypen sowie Ausnahmen ausgibt:
Listing 17.10com/tutego/insel/meta/ShowMethods.java
import java.lang.reflect.*;
class ShowMethods {
public static void main( String[] args ) {
showMethods( java.awt.Color.BLACK );
}
static void showMethods( Object o ) {
for ( Method method : o.getClass().getMethods() ) {
String returnString = method.getReturnType().getName();
System.out.print( returnString + " " + method.getName() + "(" );
Class<?>[] parameterTypes = method.getParameterTypes();
for ( int k = 0; k < parameterTypes.length; k++ ) {
String parameterString = parameterTypes[k].getName();
System.out.print( " " + parameterString );
if ( k < parameterTypes.length - 1 )
System.out.print( ", " );
}
System.out.print( " )" );
Class<?>[] exceptions = method.getExceptionTypes();
if ( exceptions.length > 0 ) {
System.out.print( " throws " );
for ( int k = 0; k < exceptions.length; k++ ) {
System.out.print( exceptions[k].getName() );
if ( k < exceptions.length - 1 )
System.out.print( ", " );
}
}
System.out.println();
}
}
}
Die Ausgabe sieht gekürzt so aus:
boolean equals( java.lang.Object )
java.lang.String toString( )
...
[F getRGBColorComponents( [F )
...
void wait( long ) throws java.lang.InterruptedException
void notify( )
void notifyAll( )
Wir bemerken an einigen Stellen eine kryptische Notation, wie etwa »[F«. Dies ist aber lediglich wieder die schon erwähnte Kodierung für Array-Typen. So gibt getRGBComponents(float[]) ein float-Array zurück.
extends AccessibleObject
implements Member, GenericDeclaration
abstract Class<?> getDeclaringClass()
Liefert das Class-Exemplar für die Klasse oder die Schnittstelle, in der die Methode deklariert wurde. Diese Methode ist Teil der Schnittstelle Member.abstract String getName()
Liefert den Namen der Methode. Diese Methode ist Teil der Schnittstelle Member.abstract int getModifiers()
Liefert die Modifizierer. Diese Methode ist Teil der Schnittstelle Member.abstract Class<?>[] getParameterTypes()
Liefert ein Array von Class-Objekten, die die Typen der Parameter beschreiben. Die Reihenfolge entspricht der deklarierten Parameterliste. Das Array hat die Länge null, wenn die Methode keine Parameter erwartet.abstract Class<?>[] getExceptionTypes()
Liefert ein Array von Class-Objekten, die mögliche Exceptions beschreiben. Das Array hat die Länge null, wenn die Methode keine solchen Exceptions mittels throws deklariert. Das Feld spiegelt nur die throws-Klausel wider. Sie kann prinzipiell auch zu viele Exceptions enthalten, bei einer Methode foo() throws RuntimeException, NullPointerException etwa genau die beiden Ausnahmen.
Da Konstruktoren keinen Rückgabetyp habe, gibt es die Methode getReturnType() nur in Method, aber nicht im Obertyp Executable:
extends Executable
Class<?> getReturnType()
Gibt ein Class-Objekt zurück, das den Ergebnistyp beschreibt.String toString()
Liefert eine String-Repräsentation der Methode, ähnlich dem Methodenkopf in einer Deklaration.
17.5.5Properties einer Bean erfragen
Eine Bean besitzt Properties (Eigenschaften), die in Java (bisher) durch Setter und Getter ausgedrückt werden, also Methoden, die einer festen Namenskonvention folgen. Gibt es Interesse an den Properties, lässt sich natürlich getMethods() auf dem Class-Objekt aufrufen und nach den Methoden filtern, die der Namenskonvention entsprechen. Die Java-Bibliothek bietet aber im Paket java.beans eine einfachere Lösung für Beans: einen PropertyDescriptor.
[zB]Beispiel
Gib alle Properties von Color aus (es gibt nur lesbare):
Listing 17.11com/tutego/insel/meta/PropertyDescriptors.java, main()
for ( PropertyDescriptor pd : beanInfo.getPropertyDescriptors() )
System.out.println( pd.getDisplayName() + " : " +
pd.getPropertyType().getName() );
Die Ausgabe:
alpha : int
blue : int
class : java.lang.Class
colorSpace : java.awt.color.ColorSpace
green : int
red : int
transparency : int
Interessanter sind vom PropertyDescriptor die Methoden getReadMethod() und getWriteMethod(), die beide ein Method-Objekt liefern – sofern es verfügbar ist –, um so die Methode gleich aufrufen zu können.
BeanInfo liefert mit getPropertyDescriptors() zwar die Properties, kann jedoch über getMethodDescriptors() auch alle anderen Methoden liefern.
17.5.6Konstruktoren einer Klasse
Konstruktoren und Methoden haben einige Gemeinsamkeiten, unterscheiden sich aber insofern, als Konstruktoren keinen Rückgabewert haben. Die Ähnlichkeit zeigt sich auch in der Methode getConstructors(), die ein Array von Constructor-Objekten zurückgibt. Über dieses Array lassen sich dann wieder Name, Modifizierer, Parameter und Exceptions der Konstruktoren einer Klasse erfragen. Wie wir in Abschnitt 17.6.1, »Objekte erzeugen«, sehen werden, lassen sich auch über die Methode newInstance(Object... initargs) neue Objekte erzeugen. Wegen der weitgehenden Ähnlichkeit der Klassen Constructor und Method sind die folgenden Methoden hier nicht näher beschrieben.
[zB]Beispiel
Zeige alle Konstruktoren der Color-Klasse:
Listing 17.12com/tutego/insel/meta/ShowConstructors.java, main()
System.out.println( c );
Die Klasse Constructor implementiert eine auskunftsfreudige toString()-Methode. Die String-Repräsentation zeigt die Signatur mit Sichtbarkeit. Nach dem Aufruf erhalten wir:
public java.awt.Color(int)
public java.awt.Color(int,int,int)
public java.awt.Color(int,int,int,int)
public java.awt.Color(java.awt.color.ColorSpace,float[],float)
public java.awt.Color(int,boolean)
public java.awt.Color(float,float,float)
implements Serializable, GenericDeclaration, Type, AnnotatedElement
Constructor[] getConstructors()
Liefert ein Feld mit Constructor-Objekten.Constructor<T> getConstructor(Class... parameterTypes)
throws NoSuchMethodException
Liefert ein ausgewähltes Constructor-Objekt.
Das, was auf dem Konstruktor (oder der Methode) möglich ist, gibt die Oberklasse Executable vor; Constructor und Method erweitern Executable. Die wichtigen Methoden:
extends AccessibleObject
implements Member, GenericDeclaration
abstract Class<T> getDeclaringClass()
Eine ziemlich langweilige Methode, da Konstruktoren nicht vererbt werden. Sie gibt immer nur jene Klasse aus, von der das Class-Objekt kommt. Das ist ein wichtiger Unterschied zwischen Methoden und Konstruktoren, der bei dieser Methode deutlich auffällt.abstract Class<?>[] getExceptionTypes()
abstract int getModifiers()
abstract String getName()
int getParameterCount()
Parameter[] getParameters()
abstract Class<?>[] getParameterTypes()
boolean isVarArgs()
Einige Methoden sind schon aus Member bekannt, denn Executable implementiert die Schnittstelle, und daraus die vier Methoden.
17.5.7Annotationen
Annotationen erfragen Methoden der Schnittstelle AnnotatedElement, die Class, Field, Executable (und damit Constructor, Method), Package und Parameter implementieren. Abschnitt 17.7, »Eigene Annotationstypen *«, kommt auf Annotationen zurück und darauf, wie sie praktisch erfragt werden.