Galileo Computing <openbook>
Galileo Computing - Programming the Net
Galileo Computing - Programming the Net


Java ist auch eine Insel von Christian Ullenboom
Programmieren für die Java 2-Plattform in der Version 1.4
Buch: Java ist auch eine Insel - Zum Katalog
gp Kapitel 22 Reflection
  gp 22.1 Einfach mal reinschauen
  gp 22.2 Mit dem Class-Objekt etwas über Klassen erfahren
    gp 22.2.1 An ein Class-Objekt kommen
    gp 22.2.2 Was das Class-Objekt beschreibt
    gp 22.2.3 Der Name der Klasse
    gp 22.2.4 Oberklassen finden
    gp 22.2.5 Implementierte Interfaces einer Klasse oder eines Inferfaces
    gp 22.2.6 Modifizierer und die Klasse Modifier
    gp 22.2.7 Die Attribute einer Klasse
    gp 22.2.8 Methoden
    gp 22.2.9 Konstruktoren einer Klasse
  gp 22.3 Objekte manipulieren
    gp 22.3.1 Objekte erzeugen
    gp 22.3.2 Die Belegung der Variablen erfragen
    gp 22.3.3 Variablen setzen
  gp 22.4 Methoden aufrufen
    gp 22.4.1 Dynamische Methodenaufrufe bei festen Methoden beschleunigen

Kapitel 22 Reflection

Aber für was ist das gut?
– Advanced Computing Systems Division von IBM, 1968, zum Microchip


Galileo Computing

22.1 Einfach mal reinschauen  downtop

Wir wollen uns im Folgenden mit einer für Java einzigartigen Technologie beschäftigten. Das Reflection-Modell erlaubt es uns, Klassen und Objekte, die zur Laufzeit von der JVM im Speicher gehalten werden, zu untersuchen und in begrenztem Umfang zu modifizieren. Das Konzept Reflection (oder auch Introspektion) wird dann besonders interessant, wenn wir uns näher mit Java-Beans beschäftigen oder wir Hilfsprogramme zum Debuggen oder GUI-Builder schreiben. Der Typ der Programmen nennt sich auch Meta-Programme, die auf den Klassen und Objekten anderer Programme operieren. Reflection fällt daher auch in die Schlagwortkategorie Meta-Programming.

In den nächsten Abschnitten versuchen wir zunächst die verfügbaren Informationen über eine Klasse zu eruieren. Dann erzeugen wir eigenständig Objekte und rufen Methoden auf. Im Unterschied zur herkömmlichen Programmierung in Java müssen wir hier beim Schreiben des Programms noch nicht wissen, wie die Klasse heißt, von der ein Exemplar erzeugt werden soll. Ein zusätzlicher Abschnitt beschäftigt sich noch mit Arrays, da sie einen Sonderfall unter den Objekten darstellen.


Galileo Computing

22.2 Mit dem Class-Objekt etwas über Klassen erfahren  downtop

Abbildung

Wir wollen einmal annehmen, dass wir einen Klassenbrowser schreiben wollen. Dieser soll alle zum laufenden Programm gehörenden Klassen anzeigen und dazu noch weitere Informationen wie Variablenbelegung, definierte Methoden, Konstruktoren und Informationen über die Vererbungshierarchie. Dafür benötigen wir die Bibliotheksklasse Class. Exemplare der Klasse Class sind Objekte, die jeweils eine Java-Klasse repräsentieren. In diesem Punkt unterscheidet sich Java von vielen herkömmlichen Programmiersprachen wie C++, da bei Java Eigenschaften von Klassen vom gerade laufenden Programm mittels dieser speziellen Objekte abgefragt werden können. Bei den Exemplaren von Class handelt es sich um eine eingeschränkte Form von Metaobjekten – eine Beschreibung einer Java-Klasse, die aber nur ausgewählte Informationen preisgibt. Neben normalen Klassen werden auch Schnittstellen durch je ein Class-Objekt repräsentiert.


Galileo Computing

22.2.1 An ein Class-Objekt kommen  downtop

Zunächst müssen wir für eine bestimmte Klasse das zugehörige Class-Objekt in Erfahrung bringen. Dies ist über mehrere Wege möglich:

gp  Ist ein Exemplar der Klasse verfügbar, so rufen wir die getClass()-Methode des Objekts auf, und wir erhalten das Class-Exemplar der zugehörigen Klasse. Dies ist dann praktisch, wenn der Typ des Objekts nicht genau bekannt ist.
gp  Haben wir schon ein Class-Objekt, sind aber nicht an ihm, sondern an dessen Vorfahren interessiert, so können wir einfach mit getSuperClass() ein Class-Objekt für die Oberklasse erhalten.
gp  Jede Klasse enthält eine Klassenvariable mit Namen .class vom Typ Class, die auf das zugehörige Class-Exemplar verweist.
gp  Eine Klasse kann auch über ihren Namen über die Klassenmethode Class.forName(String) angesprochen werden. Dadurch wird eine Klasse mit dem angegebenen Namen geladen und wir erhalten das zugehörige Class-Exemplar als Ergebnis.
Beispiel Wie wir von einem bekannten Objekt ein Class-Objekt für dessen Klasse bekommen:
Class c = derGroßeUnbekannte.getClass();

class java.lang.Object

gp  final Class getClass()
Liefert zur Laufzeit das Class-Exemplar, das die Klasse des Objekts repräsentiert.

Interessiert uns dagegen die Oberklasse zur Klasse eines Objekts, so finden wir dies über

Class c = derGroßeUnbekannte.getClass();
Class s = c.
getSuperClass();

Die Methode getSuperClass() liefert dann das Class-Exemplar für die Vaterklasse.

Wissen wir, dass die Klasse DerKleineBekannte im Paket mein/dein liegt, so nutzen wir einfach die Spracherweiterung zum Erlangen des Klassenobjekts:

Class c = mein.dein.DerKleineBekannte.class;

Haben wir zur Laufzeit die Zeichenkette klassenName mit »java.util.Date« initialisiert, so erhalten wir das Class-Exemplar für die Datumsklasse durch

Class c = Class.forName( klassenName );

Dies ist oft nur dann sinnvoll, wenn der Klassenname »java.util.Date« bei der Übersetzung des Programms noch nicht feststand. Sonst ist die vorhergehende Technik eingängiger.

Beispiel Drei Möglichkeiten, um an das Class-Objekt heranzukommen, werden aufgezeigt.

Listing 22.1   GetClassObject.java
public class GetClassObject
{
public static void main( String args[] )
{
Class c1 = java.util.Date.class;

Class c2 = new java.util.Date().getClass();

Class c3 = null;

try
{
c3 = Class.forName( "java.util.Date" );
}
catch ( ClassNotFoundException e ) { }

System.out.println( c1 );
System.out.println( c2 );
System.out.println( c3 );
}
}

Da wir uns immer nur auf eine Klasse gestützt haben, ist die Ausgabe auch immer die gleiche:

class java.util.Date
class java.util.Date
class java.util.Date
final class java.lang.Class
implements Serializable

gp  static Class forName(String className) throws ClassNotFoundException
Liefert das Class-Exemplar für die Klasse oder die Schnittstelle mit dem angegeben vollqualifizierten Namen. Die benötigte Klasse wird, falls es bisher noch nicht vom Programm benötigt wurde, gesucht und geladen. Die Methode liefert niemals null zurück. Falls die Klasse nicht geladen und eingebunden werden konnte, gibt es eine ClassNotFoundException.
gp  Class getSuperclass()
Liefert ein Class-Exemplar für die Oberklasse der durch da 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.

Wie .class implementiert ist

Wir haben gesehen, dass es mehrere Möglichkeiten gibt, um zu einer Klasse ein Klassen-Objekt, also ein Exemplar der Klasse Class, zu bekommen. Eine Variante ist über die Methode Class.forName(s),wobei die Zeichenkette s ein voll qualifizierter Klassenname ist oder eine andere Variante, in dem wir hinter einen Klassenbezeichner das Suffix ».class« stellen.

class Clazz
{
public static void main( String args[] )
{
Class foo = Clazz.class;
}
}

Das Interessante an der Konstruktion ist, dass diese Zeile mit Clazz.class vom Compiler in anderen Programmcode umgewandelt wird und dann intern forName() verwendet wird:

class Clazz
{

public static void main( String args[] )
{
Class class1 = class$LClazz == null ?
(class$LClazz = class$("Clazz")) : class$LClazz;
}

static Class class$( String s )
{
try {
return Class.forName( s );
}
catch( ClassNotFoundException classnotfoundexception )
{
throw new
NoClassDefFoundError(classnotfoundexception.getMessage());
}
}

private static Class class$LClazz;
}

Probleme für Obfuscator

Dass der Compiler automatisch Bytecode gemäß dieses veränderten Quellcodes erzeugt, führt nur dann zu unerwarteten Problemen, wenn wir einen Obfuscator über den Programmtext laufen lassen, der nachträglich den Bytecode modifiziert und damit die Bedeutung des Programms beziehungsweise des Bytecodes verschleiert und dabei Klassen umbenennt. Offensichtlich darf ein Obfuscator Klassen, deren Class-Exemplare abgefragt werden, nicht umbenennen; oder der Obfuscator müsste die entsprechenden Zeichenketten ebenfalls korrekt ersetzen. Aber natürlich nicht alle Zeichenketten, die zufällig mit Namen von Klassen übereinstimmen.


Galileo Computing

22.2.2 Was das Class-Objekt beschreibt  downtop

Ein Class-Exemplar kann eine Schnittstelle, eine Klasse, einen primitiven Datentypen oder auch einen Arraytyp beschreiben. Dies lässt sich durch die drei Methoden isInterface(), isPrimitive() und isArray() herausfinden. Wenn keine der drei Methoden für ein Class-Exemplar true liefert, repräsentiert das Objekt eine gewöhnliche Klasse.

Dass es auch Class-Exemplare gibt, die die primitiven Datentypen von Java beschreiben, erstaunt möglicherweise zunächst. Damit ist es jedoch möglich die Parameter- und Ergebnistypen beliebiger Java-Methoden einheitlich durch Class-Exemplare zu beschreiben. Dazu kodiert jede der acht Wrapperklassen, die zu den Datentypen boolean, byte, char, short, int, long, float, double gehören, und die spezielle Klasse für den Typ void eine Konstante TYPE. Benötigen wir ein Class-Objekt für den primitiven Typ int, so greifen wir mit Integer.TYPE (oder alternativ mit int.class) darauf zu. Alle Class-Exemplare für primitive Datentypen werden automatisch von der JVM erzeugt. Die Methode isPrimitve() gibt genau für diese neun besonderen Class-Exemplare true zurück, so daß sie von Repräsentanten für echte Klassen unterschieden werden können.

Das folgende Programmstück testet die Attribute von Class-Objekten systematisch durch. Wir benutzen die Methode getName(), um den Namen des Class-Objekts auszugeben. Im nächsten Abschnitt mehr dazu.

Listing 22.2   CheckClassType.java
import java.util.*;

class CheckClassType
{
public static void main( String args[] )
{
Class observer = Observer.class;
Class observable = Observable.class;
Class array = (new int[2][3][4]).getClass();
Class primitive = Integer.TYPE;

checkClassType( observer );
checkClassType( observable );
checkClassType( array );
checkClassType( primitive );
}

static void checkClassType( Class c )
{
String name = c.getName();

if ( c.isArray() )
System.out.println(name + " ist ein Array.");

else if ( c.isPrimitive() )
System.out.println(name + " ist ein primitiver Datentyp.");

else if ( c.isInterface() )
System.out.println(name + " ist eine Schnittstelle.");

else
System.out.println(name + " ist eine Klasse.");
}
}

Die Ausgabe des Programms ist nun:

java.util.Observer ist eine Schnittstelle.
java.util.Observable ist eine Klasse.
[[[I ist ein Array.
int ist ein primitiver Datentyp.
final class java.lang.Class
implements Serializable

gp  boolean isInterface()
Liefert true, falls das Class-Objekt eine Schnittstelle beschreibt.
gp  boolean isArray()
Liefert true, falls das Class-Objekt einen Arraytyp beschreibt.
gp  boolean isPrimitive()
Testet, ob das Class-Objekt einen primitiven Datentyp beschreibt.

Galileo Computing

22.2.3 Der Name der Klasse  downtop

Liegt zu einer Klasse das Class-Objekt vor, so können wir zur Laufzeit ihren voll qualifizierten Namen über die Methode getName() ausgeben. Da jede Klasse und auch jede Schnittstelle einen Namen besitzt, führt diese Funktion also jedes Mal zum Ziel. Das nachfolgende Programm macht sich dies zu Nutze und erzeugt die Ausgabe java.util.Date.

Listing 22.3   SampleName.java
import java.util.Date;

class SampleName
{
public static void main( String[] args )
{
Date d = new Date();

Class c = d.getClass();
String s = c.getName();
System.out.println( s );
}
}

In dem Beispiel ist der voll qualifizierte Name noch einfach wieder zu erkennen. Jedoch kodiert getName( )Arraytypen, die ja eine besondere Form von Klassen sind, mit einem führenden »[«. Jede Klammer steht dabei für eine Dimension des Arraytyps. Nach den Klammern folgt in einer kodierten Form der Typ der Arrayelemente. So liefert

(new int[2][3][4]).getClass().getName()

den String »[[[I«, also ein dreidimensionaler Arraytyp mit Array-Elementen vom primitiven Typ int. Der Elementtyp wird wie folgt kodiert.

Tabelle 22.1   Kodierung der Elementtypen
Kürzel Datentyp
B Byte
C Char
D Double
F Float
I Int
J Long
Lclassname; class oder interface
S Short
Z boolean

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- bzw. Interfacename 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

gp  String getName()
Liefert für ein Class-Exemplar als String den voll qualifizierten Namen der repräsentierten Klasse, oder Schnittstelle bzw. des repräsentierten Arraytyps oder primitiven Datentyps.
gp  String toString()
Liefert eine für Menschen lesbare Stringrepräsentation des Class-Objekts.

Galileo Computing

22.2.4 Oberklassen finden  downtop

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 22.4   PrintSuperClasses.java
import java.awt.*;

class PrintSuperClasses
{
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 Oberkasse für alle grafischen Komponentenklassen ist und dann Object:

java.awt.Component
java.lang.Object

Galileo Computing

22.2.5 Implementierte Interfaces einer Klasse oder eines Inferfaces  downtop

Klassen stehen zum einen in einer Vererbungsbeziehung zu einer Oberklasse und können zum anderen mehrere Schnittstellen implementieren. Schnittstellen können ihrerseits wiederum anderen 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 22.5   SampleInterface.java
class SampleInterface
{
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

Galileo Computing

22.2.6 Modifizierer und die Klasse Modifier  downtop

Eine Klassendeklaration kann Modifizierer, also Schlüsselwörter, die die Sichtbarkeit bestimmen, enthalten. Unter anderem sind dies public, protected, private und final. Sie stehen in der Klassendeklaration vor dem Schlüsselwort class. 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 stehen diese als Ganzzahl im Rückgabewert verschlüsselt.

Abbildung

final class java.lang.Class
implements Serializable

gp  int getModifiers()
Liefert die Modifizierer für eine Klasse oder eine Schnittstelle.

Damit wir uns bei der Entschlüsselung nicht mit magischen Zahlenwerten der Java Virtual Machine herumschlagen müssen, gibt es in der Klasse Modifier einige statische Methoden, die diese Ganzzahl auseinander nehmen. Zudem werden Konstanten definiert, 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- bzw. 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

gp  static boolean isAbstract( int mod )
gp  static boolean isFinal( int mod )
gp  static boolean isInterface( int mod )
gp  static boolean isNative( int mod )
gp  static boolean isPrivate( int mod )
gp  static boolean isProtected( int mod )
gp  static boolean isPublic( int mod )
gp  static boolean isStatic( int mod )
gp  static boolean isSynchronized( int mod )
gp  static boolean isTransient( int mod )
gp  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 "";
}

Galileo Computing

22.2.7 Die Attribute einer Klasse  downtop

Besonders bei Klassenbrowsern oder GUI-Buildern wird es interessant auf die Variablen eines Objekts zuzugreifen, dass heißt, ihre Werte auszulesen und zu verändern. Damit wir an beschreibende Objekte für die in einer Klasse definierten bzw. 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 Zugriff per (gewöhnlicher) Reflection. 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.

final class java.lang.Class
implements Serializable

gp  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 bzw. Schnittstelle keine öffentlichen Variablen definiert oder erbt. getFields() liefert automatisch auch Einträge für die aus Oberklassen bzw. Schnittstellen geerbten öffentlichen Variablen.
Abbildung

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

gp  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.
gp  int getModifiers()
Liefert die deklarierten Modifizierer für die Variable.
gp  String getName()
Liefert den Namen der Variablen. Diese Methode ist Teil der Schnittstelle Member.
gp  Class getType()
Liefert ein Class-Objekt, das dem Datentyp der Variablen entspricht.
gp  String toString()
Liefert eine String-Repräsentation. Zuerst wird der Zugriffsmodifizierer (public, protected oder private) mit weiteren Modifizierern (static, final, transient, volatile) ausgegeben. Es folgt 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 Stringrepräsentation des Typs.

Listing 22.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 MONTH_FIELD;
...
int MEDIUM;
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
}

Galileo Computing

22.2.8 Methoden  downtop

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 evtl. 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

gp  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 wiederspiegelt.

Abbildung

final class java.lang.reflect.Method
extends AccessibleObject implements Member

gp  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.
gp  String getName()
Liefert den Namen der Methode. Diese Methode ist Teil der Schnittstelle Member.
gp  int getModifiers()
Liefert die Modifizierer. Diese Methode ist Teil der Schnittstelle Member.
gp  Class getReturnType()
Gibt ein Class-Objekt zurück, das den Ergebnistyp beschreibt.
gp  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.
gp  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 solche Exceptions mittels throws deklariert. Das Feld spiegelt nur die throws-Klausel wieder. Sie kann prinzipiell auch zu viele Exceptions enthalten. Bei einer Funktion foo() throws RuntimeException, NullPointerException etwa genau die zwei Ausnahmen.
gp  String toString()
Liefert eine Stringrepräsentation der Methode, ähnlich dem Methodenkopf in einer Deklaration.

Wir wollen nun ein Programm schreiben, dass zusätzlich zu den Parametertypen noch die Namen erfragt.

Listing 22.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 HSBtoRGB( float,  float,  float 
)
[F RGBtoHSB( int, int, int, [F )
java.awt.Color decode( java.lang.String )\
throws java.lang.NumberFormatException
...
[F getRGBComponents( [F )
int getRed( )
int getTransparency( )

Wir bemerken an einigen Stellen eine kryptische Notation, wie etwa »[F«. Dies ist aber lediglich wieder die schon erwähnte Kodierung für Arraytypen. So gibt getRGBCompo nents() ein float-Array zurück und erwartet ein float-Array als Parameter.


Galileo Computing

22.2.9 Konstruktoren einer Klasse  downtop

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

gp  Constructor[] getConstructors()
Liefert ein Feld mit Constructor-Objekten.
final class java.lang.reflect.Constructor
extends AccessibleObject implements Member

gp  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 diese Methode deutlich auffällt.
gp  Class[] getExceptionTypes()
gp  int getModifiers()
gp  String getName()
gp  Class[] getParameterTypes()
Abbildung

Wegen der Ähnlichkeit zu getMethods() verwenden wir als Beispiel die sehr gesprächige Methode toString() zum Auflisten aller Konstruktoren.

Listing 22.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)

Galileo Computing

22.3 Objekte manipulieren  downtop

Nachdem wir nun genügend über das Ausfragen von Klassen-, Variablen, Methoden- und Konstruktorobjekten wissen, wollen wir nun aktiv werden und Objekte erzeugen, Werte von Variablen abfragen und verändern, sowie Methoden dynamisch per Reflection aufrufen.


Galileo Computing

22.3.1 Objekte erzeugen  downtop

Der new-Operator erzeugt in Java zur Laufzeit ein Exemplar einer Klasse. Der Compiler muss dazu den Namen der Klasse wissen, so dass er einen passenden Konstruktoraufruf erzeugen kann. Kennen wir aber erst später zur Laufzeit den Namen der gewünschten Klasse für unser Objekt, so fällt die new-Operation flach, denn für diesen Spezialfall ist der new-Operator nicht gedacht. Zudem kennt new Argumente, die es dem Konstruktor übergibt.

Um also Exemplare dynamisch bestimmter Klassen zu erzeugen, brauchen wir erst einmal ein passendes Class-Objekt. Wie dies erzeugt wird, ist am Anfang des Kapitels beschrieben. Nun holen wir uns mit getConstructor() ein Konstruktor-Objekt, das den gewünschten Konstruktor beschreibt. Jedes Konstruktor-Objekt reagiert auf die newInstance(Object[]) Methode, die ein neues Exemplar erschafft, indem sie den zu Grunde liegenden Konstruktor aufruft. Der Parameter der newInstance() Methode enthält dabei die an den Konstruktor zu übergebenden Parameter. Bei einem parameterlosen Konstruktor können wir einfach newInstance(null) aufrufen, sonst müssen wir ein passendes Objekt-Array erzeugen und mit Parameterwerten füllen. Glücklicherweise erlaubt die Spacherweiterung seit Java 1.1 anonyme Arrays, sodass ein Konstruktor, der zum Beispiel ein Point mit Text erzeugen möchte, mittels Reflection aufgerufen werden kann:

Listing 22.9   CreateObject.java
import java.lang.reflect.*;
import java.awt.*;

public class CreateObject
{
public static void main( String args[] )
{
try
{
Class pointClass = Point.class;

Class argsClass[] = new Class[]{int.class, int.class};
Constructor constructor =
pointClass.getConstructor( argsClass );

Object[] intArgs =
new Object[]{new Integer(10), new Integer(20)};
Point p = (Point) constructor.newInstance( intArgs );

System.out.println( p );
}
catch ( InstantiationException e ) {
System.err.println( e );
}
catch ( IllegalAccessException e ) {
System.err.println( e );
}
catch ( IllegalArgumentException e ) {
System.err.println( e );
}
catch ( InvocationTargetException e ) {
System.err.println( e );
}
catch ( NoSuchMethodException e ) {
System.err.println( e );
}
}
}
final class java.lang.reflect.Constructor
extends AccessibleObject implements Member

gp  Object newInstance( Object initargs[] )
throws InstantiationException, IllegalAccessException,
IllegalArgumentException,InvocationTargetException
Erzeugt ein neues Exemplar, indem es den durch das Constructor-Objekt repräsentierten Konstruktor mit den im Array angegebenen Parametern aufruft. Auf einige Exceptions ist zu achten: IllegalAccessException:  auf den Konstruktor kann nicht zugegriffen werden (zum Beispiel weil er private ist), IllegalArgumentException: die Anzahl der Parameter ist falsch bzw. eine Konvertierung der Parameterwerte in die benötigten Typen ist nicht möglich, InstantiationException: das Constructor-Objekt bezieht sich auf einen Konstruktor einer abstrakten Klasse, InvocationTargetException: bei der Ausführung des angegebenen Konstruktors ist eine Exception aufgetreten.

Wir müssen nun nicht erst mittels getConstructors() ein Array mit allen Constructor-Objekten zu einer Klasse holen, wenn wir nur ein Objekt mit einem bestimmten Konstruktor neu erzeugen wollen. Dazu bietet uns die Class-Klasse die Methode getConstructor() an. Damit erhalten wir ein Constructor-Objekt für den Konstruktor mit der angebenden Signatur (Parameterliste).

final class java.lang.Class
implements Serializable

gp  Constructor getConstructor( Class[] parameterTypes )
throws NoSuchMethodException, SecurityException

Galileo Computing

22.3.2 Die Belegung der Variablen erfragen  downtop

Schreiben wir einen GUI-Builder oder einen Debugger, so reicht es nicht aus, nur die Namen und Datentypen der Variablen zu wissen. Wir wollen auch auf ihre Inhalte lesend und schreibend zugreifen. Das ist durch die verschiedenen getXXX()-Methoden für Field-Objekt leicht machbar. Der erste Schritt besteht also wieder darin, ein Class-Objekt zu erfragen. Dann müssen wir über das mittels getFields() besorgte Array von Attribut-Beschreibungen ein Field-Objekt für die interessierende Variable beschaffen. Die Field-Klasse hat einige spezielle getXXX()-Methoden, um besonders einfach an die Werte von Variablen primitiven Typs zu gelangen. So liefert getFloat() einen float-Wert und getInt() ein int. Für Variablen, die eine Objektreferenz enthalten, wird einfach die get()-Methode verwendet. Wir müssen daran denken, dass IllegalArgumentException und IllegalAccessException bei falschen Zugriff auftreten können.

final class java.lang.reflect.Field
extends AccessibleObject implements Member

gp  Class getDeclaringClass()
Liefert die Klasse oder Schnittstelle, in der das Attribut deklariert wurde. Diese Methode ist Teil der Schnittstelle Member.
gp  String getName()
Liefert den Namen der Variablen. Diese Methode ist Teil der Schnittstelle Member.
gp  int getModifiers()
Liefert die Modifizierer. Diese Methode ist Teil der Schnittstelle Member.
gp  Object get( Object obj )
gp  boolean getBoolean( Object obj )
gp  byte getByte( Object obj )
gp  char getChar( Object obj )
gp  double getDouble( Object obj )
gp  float getFloat( Object obj )
gp  int getInt( Object obj )
gp  long getLong( Object obj )
gp  short getShort( Object obj )
Beispiel Wir schreiben nun ein kleines Programm, welches ein Rectangle-Objekt mit einer Belegung für x, y, Höhe und Breite erzeugt. Anschließend erfragen wir mit getField(String) das Field-Objekt für eine Beschreibung für die Variable mit dem gegebenen Namen. Das Field-Objekt gibt dann mit getXXX()den Inhalt der Variablen preis. Um das Prinzip zu demonstrieren, wird die Höhe height über die get()-Methode erfragt, die ja ein Integer-Objekt zurückgibt. Für alle anderen Werte verwenden wir aber die spezialisierte Helferfunktion getInt(). Die Helferfunktionen sind nativ implementiert.

Listing 22.10   GetFieldElements.java
import java.lang.reflect.*;

class GetFieldElements
{
public static void main( String[] args )
{
print( new java.awt.Rectangle(11,22,33,44) );
}

static void print( Object r )
{
Class c = r.getClass();

try {
Field heightField = c.getField( "height" ),
widthField = c.getField( "width" ),
xField = c.getField( "x" ),
yField = c.getField( "y" );

Integer height = (Integer) heightField.get( r );
int width = widthField.getInt( r ),
x = xField.getInt( r ),
y = yField.getInt( r );

String out = c.getName() + "[x=" + x + ",y=" + y +
",width=" + width + ",height=" + height + "]";

System.out.println( out );
System.out.println( r.toString() );
}
catch ( NoSuchFieldException e ) {
System.out.println( e );
}
catch ( SecurityException e ) {
System.out.println( e );
}
catch ( IllegalAccessException e ) {
System.out.println( e );
}
}
}

Es erzeugt nun nach dem Aufruf die Ausgabe

java.awt.Rectangle[x=11,y=22,width=33,height=44]
java.awt.Rectangle[x=11,y=22,width=33,height=44]

Galileo Computing

22.3.3 Variablen setzen  downtop

Bei Debuggern oder grafischen Editoren ist es nur eine Seite der Medaille, die Werte von Variablen anzuzeigen. Dazu kommt noch das Setzen der Werte von Variablen. Dies ist aber genauso einfach wie das Abfragen. An Stelle der getXXX()-Methoden kommen nun verschiedene setXXX()-Methoden zum Einsatz. So trägt setBoolean() einen Wahrheitswert oder setFloat() eine Fließkommazahl in eine Variable ein. Eine allgemeine set()-Methode wird für Objektreferenzen verwendet. Die Funktion set() kann jedoch auch mit dem Exemplar einer Wrapperklasse für Variablen von primitiven Datentypen benutzt werden. Die folgenden Funktionen setTyp() setzen daher alle »ihren« Datentyp. Wir müssen aber dafür sorgen, dass die Variable existiert und wir Zugriff darauf haben. In allen Fällen muss auf IllegalArgumentException und IllegalAccessException geachtet werden.

final class java.lang.reflect.Field
extends AccessibleObject implements Member

gp  void set( Object obj, Object value )
Setzt das Attribut des Objekts obj, das durch dieses Field-Objekt repräsentiert wird, auf den neuen Wert value.
gp  void setBoolean(Object obj, boolean z )
gp  void setByte( Object obj, byte b )
gp  void setChar( Object obj, char c )
gp  void setDouble( Object obj, double d )
gp  void setFloat( Object obj, float f )
gp  void setInt( Object obj, int i )
gp  void setLong( Object obj, long l )
gp  void setShort( Object obj, short s )

Das nachfolgende Programm erzeugt ein Rectangle-Objekt mit dem Konstruktor, der width und height setzt. Anschließend rufen wir mit modify() eine Methode auf, die eine Variable mit dem übergegebenen Namen verändert. Der neue Variablenwert wird modify() ebenfalls mitgegeben. Die Veränderung der Variablen erfolgt anschließend mit der set()-Methode. Da wir primitve Datentypen übergeben, wickeln wir sie für die modify()-Methode in ein Integer-Objekt ein. Andernfalls müssten wir nicht die allgemeine set()-, sondern die spezialisierte setInt()-Methode verwenden.

Listing 22.11   SetFieldElements.java
import java.lang.reflect.*;
import java.awt.*;

class SetFieldElements
{
public static void main( String args[] )
{
Rectangle r = new Rectangle( 11, 22 );
System.out.println( r );

modify( r, "width", new Integer(1111) );
modify( r, "height", new Integer(2222) );

System.out.println( r );
}

static void modify( Object o, String name, Integer param )
{
Field field;
Integer value;

Class c = o.getClass();

try
{
field = c.getField( name );
field.set( o, param );
}
catch ( NoSuchFieldException e ) {
System.err.println( e );
}
catch ( IllegalAccessException e ) {
System.err.println( e );
}
}
}

Die Ausgabe des Programms zeigt uns, wie erwartet, eine Veränderung der Breite und Höhe:

java.awt.Rectangle[x=0,y=0,width=11,height=22]
java.awt.Rectangle[x=0,y=0,width=1111,height=2222]

Galileo Computing

22.4 Methoden aufrufen  downtop

Nach dem Abfragen und Setzen von Variablenwerten und Konstruktoraufrufen zum Erzeugen eines Objekts, ist das Aufrufen von Methoden per Reflection der letzte Schritt. Wenn zur Compilezeit der Name der Methode nicht feststeht, so lässt sich zur Laufzeit dennoch eine im Programm definierte Methode aufrufen, wenn ihr Name als Zeichenkette vorliegt. Für überladene Methoden wird zur Unterscheidung auch noch die Parameterliste als Class-Objekte benötigt.

Zunächst einmal gehen wir wieder von einem Class-Objekt aus, welches die Klasse des Objekts beschreibt für das eine Objektmethode aufgerufen werden soll. Anschließend wird ein Method-Objekt als Beschreibung der gewünschten Methode benötigt; wir bekommen dies mit der Funktion getMethod()aus dem Class-Exemplar. getMethod() verlangt zwei Argumente: Einen String mit dem Namen der Methode und ein Array von Class-Objekten. Jedes Element dieses Arrays entspricht einem Parametertyp aus der Signatur der Methode. Damit werden überladene Methoden unterschieden. Nachdem wir das beschreibende Method-Exemplar und die Parameterwerte für den Aufruf vorbereitet haben, wird die Zielmethode mittels invoke() ausgeführt. Auch invoke() hat zwei Argumente: Ein Array mit Argumenten, die der aufgerufenen Methode übergeben werden, und eine Objektreferenz, welche als this-Referenz fungiert und zur Auflösung der dynamischen Bindung dient.

Abbildung

Beispiel Wir erzeugen ein Point-Objekt und setzen im Konstruktor den x-Wert auf 10. Anschließend erfragen wir mit der Methode getX(), die wir dynamisch aufrufen, den x–Wert wieder ab.

Listing 22.12   InvokeMethod.java
import java.awt.*;
import java.lang.reflect.*;

class InvokeMethod
{
public static void main( String args[] ) throws Exception
{
Point p = new Point( 10, 0 );

Method method = p.getClass().getMethod( "getX", null );
String returnType = method.getReturnType().getName();

System.out.print( "(" + returnType + ") " );

Object returnValue = method.invoke( p, null );
System.out.println( returnValue );
}
}

Und die Ausgabe ist:

(double) 10.0

Galileo Computing

22.4.1 Dynamische Methodenaufrufe bei festen Methoden beschleunigen  toptop

Werden über Reflection Methoden mit erst zur Laufzeit bestimmten Namen aufgerufen, so ist die Geschwindigkeit erbärmlich. Diese Aufrufe lassen sich prinzipbedingt auch durch einen JIT-Compiler nicht weiter beschleunigen. Wir müssen also nach einer Lösung suchen, mit der wir diese Art Aufruf beschleunigen können. Der richtige Weg hierbei ist, die Existenz der Methode meth() durch eine (abstrakte) Oberklasse bereits dem Compiler bekannt zu machen. Reflection wird dann nur noch benötigt, um eine Unterklasse mit gegebenem Namen zu laden und die normale dynamische Methodenbindung erledigt den Rest – ganz ohne versteckte Schnüre, doppelte Böden oder Spiegel (Reflection).

Beispiel Versuchen wir einmal folgenden Code nach diesem Schema zu optimieren.

Listing 22.13   DynamReflection.java
import java.lang.reflect.*;

public class DynamReflection
{
public static void main( String[] args ) throws Exception
{
Class clazz = Class.forName( "DynamReflectionMethod" );

Object o = clazz.newInstance();

Method mtd = clazz.getMethod( "meth", new Class[]{} );
mtd.invoke( o, new Object[]{} );
}
}
Listing 22.14   DynamReflectionMethod.java
public class DynamReflectionMethod
{
public void meth()
{
System.out.println("Bewusste Raucher trinken Filterkaffee" );
}
}

DynamReflection ist nun die Hauptklasse, die DynamReflectionMethod erst dann lädt, wenn Class.forName() aufgerufen wird. Dann erzeugen wir ein Exemplar von DynamReflection Method mit der Funktion newInstance() auf dem Class-Objekt. Anschließend sucht getMethod() die Beschreibung der Methode meth() heraus. Mit invoke() rufen wir diese anschließend auf. Und genau hier ist der Geschwindigkeitsverlust. Wenn wir es schaffen würden, um das invoke() herumzukommen, wäre das schon ein großer Fortschritt. Dies schaffen wir, in dem wir eine Oberklasse (oder eine Schnittstelle) für DynamReflection Method konstruieren, die dann genau diese Methode vorschreibt. Die Unterklasse wird dann eine Implementierung angeben.

Listing 22.15   DynamAbstract.java
import java.lang.reflect.*;

class DynamAbstract
{
public static void main( String args[] ) throws Exception
{
Class clazz = Class.forName( "DynamBaseMethod" );

DynamBase o = (DynamBase)clazz.newInstance();

o.meth();
}
}

Listing 22.16   DynamBase.java
public abstract class DynamBase
{
abstract void meth();
}
Listing 22.17   DynamBaseMethod.java
public class DynamBaseMethod extends 
DynamBase
{
public void meth()
{
System.out.println( "Bewusste Raucher trinken Filterkaffee" );
}
}

DynamBase ist eine abstrakte Klasse, die zur Übersetzungszeit bekannt ist. Die virtuelle Maschine wird nun den Aufruf selber nach den üblichen Regeln für die dynamische Bindung auflösen und umsetzen. Die Klasse DynamBaseMethod wird allerdings immer noch erst zur Laufzeit geladen. Wir verstecken hier sehr elegant den Aufwand. Wir haben die gleiche Funktionalität und Flexibilität wie im vorangegangenen Reflection-Beispiel, aber mit der höheren Geschwindigkeit eines konventionellen Methodenaufrufs ohne Reflection.






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.

  

Java 2




Copyright © Galileo Press GmbH 2002
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.


[Galileo Computing]

Galileo Press GmbH, Gartenstraße 24, 53229 Bonn, fon: 0228.42150.0, fax 0228.42150.77, info@galileo-press.de