![]() |
|
|||||
21.2.3 Der Name der Klasse
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Kürzel | Datentyp |
|---|---|
| B | Byte |
| C | Char |
| D | Double |
| F | Float |
| I | Int |
| J | Long |
| LKlassenname; | Klasse oder Schnittstelle |
| S | Short |
| Z | Boolean |
Tabelle 21.1 Kodierung der Elementtypen
Nimmt das Array Objektreferenzen auf, wird deren Typ in der Form »LKlassenname;« kodiert. So ergibt
(new Object[3]).getClass().getName()
den String »[Ljava.lang.Object;«. Der Klassen- beziehungsweise Schnittstellenname ist dabei wieder voll qualifiziert.
Auch eine zweite Methode ist uns bekannt, um Class-Exemplare für Menschen lesbar auszugeben: die Methode toString(). Sie basiert im Kern auf getName(), fügt aber zusätzlich noch die Art der repräsentierten Klasse (normale Klasse, Schnittstelle oder primitiver Datentyp) ein:
public String toString() { return (isInterface() ? "interface " : (isPrimitive() ? "" : "class ")) + getName(); }
final class java.lang.Class implements Serializable |
| String getName() Liefert für ein Class-Exemplar als String den voll qualifizierten Namen der repräsentierten Klasse oder Schnittstelle beziehungsweise des repräsentierten Array-Typs oder primitiven Datentyps. |
| String toString() Liefert eine für Menschen lesbare String-Repräsentation des Class-Objekts. |
Das Class-Exemplar für eine Klasse speichert die Position der Klasse in der Vererbungshierarchie, die Sichtbarkeitsstufe der Klasse und weitere Informationen. Um die Oberklasse zu ermitteln, wird getSuperclass() verwendet. Die Methode gibt null zurück, falls das Class-Objekt eine Schnittstelle repräsentiert oder wir schon am oberen Ende der Hierarchie sind, also bei dem Class-Objekt für die Wurzelklasse Object. Das folgende Programm findet alle Oberklassen einer Klasse durch den wiederholten Aufruf der Methode getSuperclass().
Listing 21.4 ShowSuperclasses.java
import java.awt.*; class ShowSuperclasses { public static void main( String args[] ) { printSuperclasses( new Button() ); } static void printSuperclasses( Object o ) { Class subclass = o.getClass(); Class superclass = subclass.getSuperclass(); while ( superclass != null ) { String className = superclass.getName(); System.out.println( className ); subclass = superclass; superclass = subclass.getSuperclass(); } } }
Wahrscheinlich wäre eine rekursive Variante noch eleganter, aber darauf kommt es jetzt nicht an. Die Ausgabe des Programms liefert zunächst Component, da dies die gemeinsame Oberklasse für alle grafischen Komponentenklassen ist, und dann Object:
java.awt.Component java.lang.Object
final class java.lang.Class implements Serializable |
| Class getSuperclass() Liefert ein Class-Exemplar für die Oberklasse der durch das aufrufende Class-Objekt repräsentierten Klasse. Falls wir schon oben auf der Vererbungshierarchie bei Object sind oder wir nach der Oberklasse einer Schnittstelle fragen, dann liefert die Funktion null. |
Klassen stehen zum einen in einer Vererbungsbeziehung zu einer Oberklasse und können zum anderen mehrere Schnittstellen implementieren. Schnittstellen können ihrerseits wiederum andere Schnittstellen erweitern. In einer Klassendefinition folgt direkt hinter dem Schlüsselwort implements eine Auflistung der implementierten Schnittstellen. So implementiert die Klasse RandomAccessFile die Schnittstellen DataOutput und DataInput.
public class RandomAccessFile implements DataOutput, DataInput
Um zu einem vorhandenen Class-Objekt die Schnittstellen aufzulisten, rufen wir getInterfaces() auf, die uns ein Array von Class-Objekten liefert. Von hier aus kennen wir den Weg zum Namen. Der Aufruf von getName() liefert den String für den Namen der Schnittstelle. Wir bleiben bei unserem Beispiel und entwickeln ein kleines Programm, das die implementierten Schnittstellen ausgibt.
Listing 21.5 ShowInterface.java
class ShowInterface { public static void main( String args[] ) { printInterfaceNames( java.io.RandomAccessFile.class ); } static void printInterfaceNames( Class clazz ) { Class theInterfaces[] = clazz.getInterfaces(); for ( int i = 0; i < theInterfaces.length; i++ ) System.out.println( theInterfaces[i].getName() ); } }
Die Ausgabe ist dann:
java.io.DataOutput java.io.DataInput
Eine Klassendeklaration kann Modifizierer, also Schlüsselwörter, die zum Beispiel die Sichtbarkeit bestimmen, enthalten. Unter anderem sind dies public, protected, private und final. Sie stehen etwa in der Klassendeklaration vor dem Schlüsselwort class oder auch vor Methoden. Die Modifizierer können auch kombiniert werden, so ist die Klasse Class selbst public final. Um an die Modifizierer zu gelangen, wird die Methode getModifiers() verwendet, dann sind die Sichtbarkeiten als Ganzzahl im Rückgabewert verschlüsselt.
final class java.lang.Class implements Serializable |
| int getModifiers() Liefert die Modifizierer für eine Klasse oder eine Schnittstelle. |
Damit wir uns bei der Entschlüsselung nicht mit magischen Zahlenwerten der JVM herumschlagen müssen, gibt es in der Klasse java.lang.reflect.Modifier einige statische Methoden, die diese Ganzzahl testen. Zudem werden Konstanten definiert (wie Modifier.PUBLIC), mit denen dann dieser Integerwert verglichen werden kann. Da allerdings oftmals die Ganzzahl potenziell eine Kombination mehrerer Modifizierer kodiert, ist die gezielte Abfrage mit den isXXX()-Methoden einfacher. Obwohl eine Klasse nicht transient, synchronized, nativ sein kann, listen wir hier alle Methoden auf, da wir diese Modifizierer später auch für die Untersuchung von Methoden und Objekt- beziehungsweise Klassenvariablen per Reflection einsetzen. Jede der Testmethoden liefert true, falls der gefragte Modifizierer in dem kodierten Ganzzahlwert enthalten ist. Alle Methoden sind static und liefern ein boolean-Ergebnis, außer toString().
class java.lang.reflect.Modifier |
| static boolean isAbstract( int mod ) static boolean isFinal( int mod ) static boolean isInterface( int mod ) static boolean isNative( int mod ) static boolean isPrivate( int mod ) static boolean isProtected( int mod ) static boolean isPublic( int mod ) static boolean isStatic( int mod ) static boolean isSynchronized( int mod ) static boolean isTransient( int mod ) static boolean isVolatile( int mod ) |
| Beispiel Wir betrachten die toString()-Methode der Klasse Modifier. Dort finden wir eine Liste aller möglichen Modifizierer mit den Konstanten. |
public static String toString( int mod ) { StringBuffer sb = new StringBuffer(); int len; if ((mod & PUBLIC) != 0) sb.append("public "); if ((mod & PRIVATE) != 0) sb.append("private "); if ((mod & PROTECTED) != 0) sb.append("protected "); /* Canonical order */ if ((mod & ABSTRACT) != 0) sb.append("abstract "); if ((mod & STATIC) != 0) sb.append("static "); if ((mod & FINAL) != 0) sb.append("final "); if ((mod & TRANSIENT) != 0) sb.append("transient "); if ((mod & VOLATILE) != 0) sb.append("volatile "); if ((mod & NATIVE) != 0) sb.append("native "); if ((mod & SYNCHRONIZED) != 0) sb.append("synchronized "); if ((mod & INTERFACE) != 0) sb.append("interface "); if ((len = sb.length()) > 0)/* trim trailing space */ return sb.toString().substring(0, len-1); return ""; }
Besonders bei Klassen-Browsern oder GUI-Buildern wird 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 definierten beziehungsweise aus Oberklassen geerbten Variablen gelangen, rufen wir die Methode getFields() für das Class-Objekt der interessierenden Klasse auf. 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 privilegiertes Reflection gehen wir hier nicht ein. Schnittstellen definieren ja bekanntlich nur Konstanten. Somit ist der schreibende Zugriff, den wir später näher betrachten wollen, nur auf in Klassen definierte 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.

Hier klicken, um das Bild zu Vergrößern
final class java.lang.Class implements Serializable |
| Field[] getFields() throws SecurityException Liefert ein Array mit Field-Objekten. Die Einträge sind unsortiert. Das Array hat die Länge 0, wenn die Klasse beziehungsweise Schnittstelle keine öffentlichen Variablen definiert oder erbt. getFields() liefert automatisch auch Einträge für die aus Oberklassen beziehungsweise Schnittstellen geerbten öffentlichen Variablen. |
Die Klasse Field implementiert im Übrigen das Interface Member und ist eine Erweiterung von AccessibleObject. AccessibleObject ist die Basisklasse für Field-, Method- und Constructor-Objekte. Auch Method und Constructor implementieren das Interface Member, welches zur Identifikation über getName() oder getModifiers() dient. Zusätzlich liefert getDeclaringClass() das Class-Objekt, das tatsächlich eine Variable oder Methode definiert. Da geerbte Elemente in der Aufzählung mit auftauchen, ist dies der einzige Weg, um die Position der Definition in der Vererbungshierarchie exakt zu bestimmen.
Das Field-Objekt lässt sich vieles fragen: nach dem Namen des Attributs, nach dem Datentyp und auch wieder nach den deklarierten Modifizierern. Werfen wir einen Blick auf die toString()-Methode der Klasse Field:
public String toString() { int mod = getModifiers(); return (((mod == 0) ? "" : (Modifier.toString(mod) + " ")) + getTypeName(getType()) + " " + getTypeName(getDeclaringClass()) + "." + getName()); }
final class java.lang.reflect.Field extends AccessibleObject implements Member |
| Class getDeclaringClass() Liefert das Class-Exemplar für die Klasse oder die Schnittstelle, in der die Variable definiert wurde. Diese Methode ist Teil der Schnittstelle Member. |
| int getModifiers() Liefert die deklarierten Modifizierer für die Variable. |
| String getName() Liefert den Namen der Variablen. Diese Methode ist Teil der Schnittstelle Member. |
| Class getType() Liefert ein Class-Objekt, das dem Datentyp der Variablen entspricht. |
| String toString() Liefert eine String-Repräsentation. Zuerst wird der Zugriffsmodifizierer (public, protected oder private) mit weiteren Modifizierern (static, final, transient, volatile) ausgegeben. Dann kommt der Datentyp, gefolgt vom voll qualifizierten Namen der definierenden Klasse und schließlich der Name der Variablen. |
Um für eine Klasse alle Objekt- und Klassenvariablen mit ihren Datentypen herauszufinden, müssen wir lediglich eine Schleife über das Attribut-Array laufen lassen. Die Namen der Variablen finden sich leicht mit getName(). Aber nun haben wir den zugehörigen Datentyp noch nicht. Dazu müssen wir erst mit getType() ein Class-Objekt für den Typ ermitteln, und dann liefert uns getName() eine String-Repräsentation des Typs.
Listing 21.6 ShowFields.java
import java.lang.reflect.*; class ShowFields { public static void main( String args[] ) { printFieldNames( new java.text.SimpleDateFormat() ); } static void printFieldNames( Object o ) { Class c = o.getClass(); System.out.println( "class " + c.getName() + " {" ); Field publicFields[] = c.getFields(); for ( int i = 0; i < publicFields.length; i++ ) { String fieldName = publicFields[i].getName(); Class typeClass = publicFields[i].getType(); String fieldType = typeClass.getName(); System.out.println(" "+fieldType+" "+fieldName+";"); } System.out.println( "}" ); } }
Dies ergibt die (gekürzte) Ausgabe:
class java.text.SimpleDateFormat { int ERA_FIELD; int YEAR_FIELD; ... int SHORT; int DEFAULT; }
Kürzer und noch ausführlicher geht es mit der toString()-Methode. So liefert
for ( int i = 0; i < publicFields.length; i++ ) System.out.println( " " + publicFields[i] );
etwa
class java.text.SimpleDateFormat { public static final int java.text.DateFormat.ERA_FIELD 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 }
Um herauszufinden, welche Methoden eine Klasse besitzt, wenden wir eine ähnliche Vorgehensweise an, wie wir sie auch schon bei den Variablen benutzt haben: 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 über invoke() die durch ein Method-Exemplar repräsentierte Methode auch aufrufen lässt.
final class java.lang.Class implements Serializable |
| Method[] getMethods() throws SecurityException 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, und die Länge des Arrays ist null, wenn es keine öffentlichen Methoden gibt. |
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.

Hier klicken, um das Bild zu Vergrößern
final class java.lang.reflect.Method extends AccessibleObject implements Member |
| Class getDeclaringClass() Liefert das Class-Exemplar für die Klasse oder die Schnittstelle, in der die Methode definiert wurde. Diese Methode ist Teil der Schnittstelle Member. |
| String getName() Liefert den Namen der Methode. Diese Methode ist Teil der Schnittstelle Member. |
| int getModifiers() Liefert die Modifizierer. Diese Methode ist Teil der Schnittstelle Member. |
| Class getReturnType() Gibt ein Class-Objekt zurück, das den Ergebnistyp beschreibt. |
| Class[] getParameterTypes() Liefert ein Array von Class-Objekten, die die Typen der Parameter beschreiben. Die Reihenfolge entspricht der deklarierten Parameterliste. Das Array hat eine Länge von null, falls die Methode keine Parameter erwartet. |
| Class[] getExceptionTypes() Liefert ein Array von Class-Objekten, die mögliche Exceptions beschreiben. Das Array hat eine Länge von null, falls 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 Funktion foo() throws RuntimeException, NullPointerException etwa genau die zwei Ausnahmen. |
| String toString() Liefert eine String-Repräsentation der Methode, ähnlich dem Methodenkopf in einer Deklaration. |
Wir wollen nun ein Programm schreiben, das zusätzlich zu den Parametertypen noch die Namen erfragt.
Listing 21.7 ShowMethods
import java.lang.reflect.*; class ShowMethods { public static void main( String args[] ) { showMethods( new java.awt.Color(1234) ); } static void showMethods( Object o ) { Class c = o.getClass(); Method theMethods[] = c.getMethods(); for ( int i = 0; i < theMethods.length; i++ ) { // Rückgabewert String returnString = theMethods[i].getReturnType().getName(); System.out.print( returnString + " " ); // Methodenname String methodString = theMethods[i].getName(); System.out.print( methodString + "(" ); // Parameter Class parameterTypes[] = theMethods[i].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( " )"); // Exceptions Class exceptions[] = theMethods[i].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:
int hashCode( ) 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() ein float-Array zurück und erwartet ein float-Array als Parameter.
Konstruktoren und Methoden haben einige Gemeinsamkeiten, unterscheiden sich aber in dem Punkt, dass 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 an einer späteren Stelle sehen werden, lassen sich auch über die Methode newInstance() neue Objekte erzeugen. Wegen der weitgehenden Ähnlichkeit der Klassen Constructor und Method sind die folgenden Methoden hier nicht näher beschrieben.
final class java.lang.Class implements Serializable |
| Constructor[] getConstructors() Liefert ein Feld mit Constructor-Objekten. |
final class java.lang.reflect.Constructor extends AccessibleObject implements Member |
| Class getDeclaringClass() Eine ziemlich langweilige Funktion, da Konstruktoren nicht vererbt werden. Somit wird immer nur die Klasse ausgegeben, vor der das Class-Objekt kommt. Das ist ein wichtiger Unterschied zwischen Methoden und Konstruktoren, der bei dieser Methode deutlich auffällt. |
| Class[] getExceptionTypes() int getModifiers() String getName() Class[] getParameterTypes() |

Hier klicken, um das Bild zu Vergrößern
Wegen der Ähnlichkeit zu getMethods() verwenden wir als Beispiel die sehr gesprächige Methode toString() zum Auflisten aller Konstruktoren.
Listing 21.8 ShowConstructor.java
import java.lang.reflect.*; class ShowConstructor { public static void main( String args[] ) { Class colorClass = java.awt.Color.class; Constructor theConstructors[] = colorClass.getConstructors(); for ( int i = 0; i < theConstructors.length; i++ ) System.out.println( theConstructors[i] ); } }
Nach dem Aufruf erhalten wir:
public java.awt.Color(float,float,float) public java.awt.Color(float,float,float,float) public java.awt.Color(int) public java.awt.Color(int,int,int) public java.awt.Color(int,int,int,int) public java.awt.Color(int,boolean) public java.awt.Color(java.awt.color.ColorSpace,float[],float)
1 Echte Metaklassen wären Klassen, deren jeweils einziges Exemplar die normale Java-Klasse ist. Dann wären etwa die normalen Klassenvariablen in Wahrheit Objektvariablen in der Metaklasse.
| << zurück |
Copyright (c) Galileo Press GmbH 2004
Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken. Ansonsten unterliegt das <openbook> denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt. Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.