From 33613a85afc4b1481367fbe92a17ee59c240250b Mon Sep 17 00:00:00 2001
From: Sven Eisenhauer
+Wir wollen uns ein erstes Beispiel für die Anwendung des Reflection-APIs
+ansehen. In der Praxis stellt sich immer wieder das Problem, wohin
+bei neu entwickelten Klassen der Code zum Testen der Klasse
+geschrieben werden soll. Ein Weg ist der, an das Ende der Klasse eine
+Methode public static void main
+zu hängen und den Testcode dort zu platzieren:
+
+
+
+
+
+
+ Titel
+ Inhalt
+ Suchen
+ Index
+ DOC
+ Handbuch der Java-Programmierung, 5. Auflage
+
+ <<
+ <
+ >
+ >>
+ API
+ Kapitel 43 - Reflection
+
+
+
+
+
+43.3 Methoden- und Konstruktorenaufrufe
+
+
+
+
+
+
+
+43.3.1 Parameterlose Methoden
+
+
+
+
+Listing 43.2: Testcode in der main-Methode
+
+
+
+
+
+001 public class Queue
+002 {
+003 //...
+004 //Implementierung der Queue
+005 //...
+006
+007 //---Testcode------------------------------
+008 public static void main(String[] args)
+009 {
+010 Queue q = new Queue();
+011 //...
+012 //Code zum Testen der Queue
+013 //...
+014 }
+015 }
+
+
+Auf diese Weise läßt sich der Testcode einer Dienstleistungsklasse +- wie beispielsweise einer Queue - ganz einfach mit dem Java-Interpreter +aufrufen und reproduzierbar testen. Nachteilig ist natürlich, +dass der eigentliche Code und der Testcode vermischt werden. Dadurch +wird die Klassendatei unnötig groß, was im Hinblick auf +gute Downloadzeiten nicht wünschenswert ist. Besser wäre +es, wenn der Testcode in einer separaten Klasse verbleiben würde. +Wir wollen dazu ein kleines Programm zum Testen von Java-Klassen schreiben. +Es soll folgende Eigenschaften besitzen: +
+Der Schlüssel zur Implementierung der Klasse Test +liegt in der Anwendung des Reflection-APIs. Das Laden der Testklasse +entspricht dem vorigen Beispiel, zum Aufzählen aller Methoden +bedienen wir uns der Methode getMethods +der Klasse Class: +
+
+
++public Method[] getMethods() + throws SecurityException ++ + |
++java.lang.Class | +
+getMethods +liefert ein Array von Objekten des Typs Method, +das für jede öffentliche Methode der Klasse ein Element +enthält. Um auch die nicht-öffentlichen Methoden aufzulisten, +kann die Methode getDeclaredMethods +verwendet werden. Die Klasse Method +stellt einige Methoden zum Zugriff auf das Methodenobjekt zur Verfügung. +Die wichtigsten sind: +
+
+
++String getName() + +int getModifiers() + +Class[] getParameterTypes() + +Object invoke(Object obj, Object[] args) ++ + |
++java.lang.reflect.Method | +
+Mit getName +kann der Name der Methode ermittelt werden. getModifiers +liefert eine bitverknüpfte Darstellung der Methodenattribute +(static, +private +usw.). Der Rückgabewert kann an die statischen Methoden der Klasse +Modifier +übergeben werden, um festzustellen, welche Attribute die Methode +besitzt: + + + + +
+
+
++static boolean isAbstract(int mod) +static boolean isExplicit(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 isStrict(int mod) +static boolean isSynchronized(int mod) +static boolean isTransient(int mod) +static boolean isVolatile(int mod) ++ + |
++java.lang.reflect.Modifier | +
+getParameterTypes +liefert ein Array mit Objekten des Typs Class, +das dazu verwendet werden kann, die Anzahl und Typen der formalen +Argumente der Methode festzustellen. Jedes Array-Element repräsentiert +dabei die Klasse des korrespondierenden formalen Arguments. Hat das +Array die Länge 0, so ist die Methode parameterlos. Gibt es beispielsweise +zwei Elemente mit den Typen String +und Double, +so besitzt die Methode zwei Parameter, die vom Typ String +und Double +sind. + +
+Um auch primitive Typen auf diese Weise darstellen zu können, +gibt es in den Wrapper-Klassen der primitiven Typen (siehe Abschnitt 10.2) +jeweils ein statisches Class-Objekt +mit der Bezeichnung TYPE, +das den zugehörigen primitiven Datentyp bezeichnet. Ein int-Argument +wird also beispielsweise dadurch angezeigt, dass der Rückgabewert +von getParameterTypes +an der entsprechenden Stelle ein Objekt des Typs Integer.TYPE +enthält. Insgesamt gibt es neun derartige +Klassenobjekte, und zwar für die acht primitiven Typen und für +den »leeren« Rückgabewert void: + +
+
| Klassenobjekt | +Typ |
| Boolean.TYPE + | +boolean |
| Character.TYPE | +char |
| Byte.TYPE + | +byte |
| Short.TYPE + | +short |
| Integer.TYPE + | +int |
| Long.TYPE + | +long |
| Float.TYPE + | +float |
| Double.TYPE + | +double |
| Void.TYPE + | +void |
+Tabelle 43.1: Klassenobjekte für die primitiven Typen
++
![]() |
+![]() |
+
+
+ +Alternativ zur .TYPE-Notation kann auch die in Abschnitt 43.3.2 +vorgestellte .class-Notation verwendet werden, um Klassenobjekte für +primitive Typen zu erzeugen. Dazu ist einfach der Name des gewünschten +Typs um ».class« zu ergänzen, also z.B. boolean.class +oder void.class zu schreiben. +Der Compiler erzeugt dann ein passendes Klassenobjekt für den +primitiven Typ. |
+
+
|
+![]() |
+
+Die Methode invoke +der Klasse Method +dient dazu, die durch dieses Methodenobjekt repräsentierte Methode +tatsächlich aufzurufen. Das erste Argument obj +gibt dabei das Objekt an, auf dem die Methode ausgeführt werden +soll. Es muss natürlich zu einem Objekt der Klasse gehören, +auf der getMethods +aufgerufen wurde. Das zweite Argument übergibt die aktuellen +Parameter an die Methode. Ähnlich wie bei getParameterTypes +wird auch hier ein Array angegeben, dessen Elemente den korrespondierenden +aktuellen Argumenten entsprechen. Bei Objektparametern ist einfach +ein Objekt des passenden Typs an der gewünschten Stelle zu platzieren. +
+
![]() |
+![]() |
+
+
+ +Besitzt die Methode auch primitive Argumente, wird eine automatische +Konvertierung vorgenommen (unwrapping), +indem das entsprechende Array-Element in den passenden primitiven +Datentyp konvertiert wird. Erwartet die Methode beispielsweise ein +int, +so ist ein Integer-Objekt +zu übergeben, das dann beim Aufruf automatisch »ausgepackt« +wird. |
+
+
|
+![]() |
+
+Die Implementierung der Klasse Test +sieht so aus: + + +
+
+
+
+001 /* Test.java */
+002
+003 import java.lang.reflect.*;
+004
+005 public class Test
+006 {
+007 public static Object createTestObject(String name)
+008 {
+009 //Klassennamen zusammenbauen
+010 int pos = name.lastIndexOf('.');
+011 if (pos == -1) {
+012 name = "Test" + name;
+013 } else {
+014 name = name.substring(0, pos + 1) + "Test" +
+015 name.substring(pos + 1);
+016 }
+017 //Klasse laden
+018 Object ret = null;
+019 try {
+020 Class testclass = Class.forName(name);
+021 //Testobjekt instanzieren
+022 System.out.println("==============================");
+023 System.out.println("Instanzieren von: " + name);
+024 System.out.println("--");
+025 ret = testclass.newInstance();
+026 } catch (ClassNotFoundException e) {
+027 System.err.println("Kann Klasse nicht laden: " + name);
+028 } catch (InstantiationException e) {
+029 System.err.println("Fehler beim Instanzieren: " + name);
+030 } catch (IllegalAccessException e) {
+031 System.err.println("Unerlaubter Zugriff auf: " + name);
+032 }
+033 return ret;
+034 }
+035
+036 public static void runTests(Object tester)
+037 {
+038 Class clazz = tester.getClass();
+039 Method[] methods = clazz.getMethods();
+040 int cnt = 0;
+041 for (int i = 0; i < methods.length; ++i) {
+042 //Methodenname muss mit "test" anfangen
+043 String name = methods[i].getName();
+044 if (!name.startsWith("test")) {
+045 continue;
+046 }
+047 //Methode muss parameterlos sein
+048 Class[] paras = methods[i].getParameterTypes();
+049 if (paras.length > 0) {
+050 continue;
+051 }
+052 //Methode darf nicht static sein
+053 int modifiers = methods[i].getModifiers();
+054 if (Modifier.isStatic(modifiers)) {
+055 continue;
+056 }
+057 //Nun kann die Methode aufgerufen werden
+058 ++cnt;
+059 System.out.println("==============================");
+060 System.out.println("Aufgerufen wird: " + name);
+061 System.out.println("--");
+062 try {
+063 methods[i].invoke(tester, new Object[0]);
+064 } catch (Exception e) {
+065 System.err.println(e.toString());
+066 }
+067 }
+068 if (cnt <= 0) {
+069 System.out.println("Keine Testmethoden gefunden");
+070 }
+071 }
+072
+073 public static void main(String[] args)
+074 {
+075 if (args.length <= 0) {
+076 System.err.println("Aufruf: java Test <KlassenName>");
+077 System.exit(1);
+078 }
+079 Object tester = createTestObject(args[0]);
+080 runTests(tester);
+081 }
+082 }
+
+ |
++Test.java | +
+Das Hauptprogramm ruft zunächst die Methode createTestObject +auf, um ein Objekt der Testklasse zu generieren. Falls als Argument +also beispielsweise Queue übergeben +wurde, wird ein Objekt des Typs TestQueue +erzeugt. Ist es nicht vorhanden oder kann nicht instanziert werden, +liefert die Methode null +als Rückgabewert. + +
+Anschließend wird das Testobjekt an die Methode runTests +übergeben. Diese besorgt sich das Klassenobjekt und ruft getMethods +auf. Das zurückgegebene Array repräsentiert die Liste aller +öffentlichen Methoden und wird elementweise durchlaufen. Zunächst +wird überprüft, ob der Methodenname mit test +anfängt. Ist das der Fall, wird geprüft, ob die Methode +parameterlos ist und nicht das static-Attribut +besitzt. Sind auch diese Bedingungen erfüllt, kann die Methode +mit invoke +aufgerufen werden. Als erstes Argument wird das Testobjekt übergeben. +Als zweites folgt ein leeres Array des Typs Object, +um anzuzeigen, dass keine Parameter zu übergeben sind. +
+
![]() |
+
+
+ +Beachten Sie, dass am Anfang des Programms das Paket java.lang.reflect +eingebunden wurde. Während die Klassen Class +und Object +aus historischen Gründen in java.lang +liegen (und deshalb automatisch importiert werden), liegen sie für +die übrigen Bestandteile des Reflection-APIs in java.lang.reflect +und müssen deshalb explizit importiert werden. |
+
+
|
+![]() |
+
+Eine beispielhafte Implementierung der Klasse TestQueue +könnte etwa so aussehen: + + +
+
+
+
+001 public class TestQueue
+002 {
+003 public TestQueue()
+004 {
+005 //Intialisierungen, z.B. Erzeugen eines zu
+006 //testenden Queue-Objekts
+007 }
+008
+009 public void test1()
+010 {
+011 //Erste Testmethode
+012 }
+013
+014 public void test2()
+015 {
+016 //Zweite Testmethode
+017 }
+018
+019 //...
+020 }
+
+ |
+
+Ein Aufruf von
+
+
+java Test Queue
+
+
+
+
+würde nun ein neues Objekt des Typs TestQueue +instanzieren und nacheinander die Methoden test1, +test2 usw. aufrufen. + + + + +
+In diesem Abschnitt wollen wir uns den Aufruf parametrisierter +Methoden ansehen, was nach den Ausführungen des vorigen Abschnitts +nicht mehr schwierig ist. Als Beispiel soll ein Programm geschrieben +werden, das die in Java nicht vorhandenen Funktionszeiger simuliert. +Es soll eine Methode enthalten, die eine Wertetabelle für eine +mathematische Funktion erzeugt, deren Name als String übergeben +wurde. Nach den Überlegungen des vorigen Abschnitts können +wir uns gleich das Listing ansehen: + + +
+
+
+
+001 /* FloatTables.java */
+002
+003 import java.lang.reflect.*;
+004
+005 public class FloatTables
+006 {
+007 public static double times2(double value)
+008 {
+009 return 2 * value;
+010 }
+011
+012 public static double sqr(double value)
+013 {
+014 return value * value;
+015 }
+016
+017 public static void printTable(String methname)
+018 {
+019 try {
+020 System.out.println("Wertetabelle fuer " + methname);
+021 int pos = methname.lastIndexOf('.');
+022 Class clazz;
+023 if (pos == -1) {
+024 clazz = FloatTables.class;
+025 } else {
+026 clazz = Class.forName(methname.substring(0, pos));
+027 methname = methname.substring(pos + 1);
+028 }
+029 Class[] formparas = new Class[1];
+030 formparas[0] = Double.TYPE;
+031 Method meth = clazz.getMethod(methname, formparas);
+032 if (!Modifier.isStatic(meth.getModifiers())) {
+033 throw new Exception(methname + " ist nicht static");
+034 }
+035 Object[] actargs = new Object[1];
+036 for (double x = 0.0; x <= 5.0; x += 1) {
+037 actargs[0] = new Double(x);
+038 Double ret = (Double)meth.invoke(null, actargs);
+039 double result = ret.doubleValue();
+040 System.out.println(" " + x + " -> " + result);
+041 }
+042 } catch (Exception e) {
+043 System.err.println(e.toString());
+044 }
+045 }
+046
+047 public static void main(String[] args)
+048 {
+049 printTable("times2");
+050 printTable("java.lang.Math.exp");
+051 printTable("sqr");
+052 printTable("java.lang.Math.sqrt");
+053 }
+054 }
+
+ |
++FloatTables.java | +
+Das Hauptprogramm ruft die Methode printTable +viermal auf, um die Wertetabellen zu den statischen Funktionen times2, +java.lang.Math.exp, sqr +und java.lang.Math.sqrt zu erzeugen. +Sie kann sowohl mit lokal definierten Methoden umgehen als auch mit +solchen, die in einer anderen Klasse liegen (in diesem Fall sogar +aus einem anderen Paket). + +
+In Zeile 021 wird zunächst +der am weitesten rechts stehende Punkt im Methodennamen gesucht. Ist +ein Punkt vorhanden, wird der String an dieser Stelle aufgeteilt. +Der links davon stehende Teil wird als Klassenname angesehen, der +rechts davon stehende als Methodenname. Gibt es keinen Punkt, wird +als Klassenname der Name der eigenen Klasse verwendet. Anschließend +wird das zugehörige Klassenobjekt geladen. +
+
![]() |
+![]() |
+
+
+ +Ist der Klassenname zur Compilezeit bekannt, kann anstelle des Aufrufs +von forName +die abkürzende Schreibweise .class +verwendet werden. Das hat den Vorteil, dass bereits der Compiler überprüfen +kann, ob die genannte Klasse vorhanden ist. |
+
+
|
+![]() |
+
+Anders als im vorigen Abschnitt generiert das Programm nun nicht eine +Liste aller Methoden, sondern sucht mit getMethod +ganz konkret nach einer bestimmten: +
+
+
++public Method getMethod(String name, Class[] parameterTypes) ++ + |
++java.lang.Class | +
+Dazu müssen der Name der Methode und eine Beschreibung ihrer +formalen Argumente an getMethod +übergeben werden. Auch hier werden die Argumente durch ein Array +mit korrespondierenden Klassenobjekten repräsentiert. Da die +Methoden, die in diesem Fall aufgerufen werden sollen, nur ein einziges +Argument vom Typ double +haben sollen, hat unsere Parameterspezifikation formParas +lediglich ein einziges Element Double.TYPE. +Wurde keine solche Methode gefunden oder besitzt sie nicht das static-Attribut, +löst getMethods +eine Ausnahme des Typs NoSuchMethodException +aus. +
+
![]() |
+![]() |
+
+
+
+Würde die Methode anstelle des primitiven Typs double
+ein Argument des Referenztyps Double
+erwarten, hätten wir ein Klassenobjekt der Klasse Double
+übergeben müssen. Dafür gibt es verschiedene Möglichkeiten,
+beispielsweise die beiden folgenden:
+
+
+oder
+
+ |
+
+
|
+![]() |
+
+Auch eine parametrisierte Methode kann mit invoke +aufgerufen werden. Im Unterschied zur parameterlosen muss nun allerdings +ein nicht-leeres Object-Array +mit den aktuellen Argumenten übergeben werden. Hier zeigt sich +ein vermeintliches Problem, denn in einem Array vom Object[] +können keine primitiven Typen abgelegt werden. Um Methoden mit +primitiven Parametern aufrufen zu können, werden diese einfach +in die passende Wrapper-Klasse verpackt (siehe Abschnitt 10.2). +Beim Aufruf von invoke +werden sie dann automatisch »ausgepackt« und dem primitiven +Argument zugewiesen. + +
+Wir verpacken also den zu übergebenden Wert in ein Double-Objekt +und stellen dieses in Zeile 037 +in das Array mit den aktuellen Argumenten. Beim Methodenaufruf in +der nächsten Zeile wird es dann automatisch ausgepackt und steht +innerhalb der Methode als double-Wert +zur Verfügung. Das Programm ruft die Methode für jeden der +Werte 0.0, 1.0, 2.0, 3.0, 4.0 und 5.0 auf und erzeugt so eine einfache +Wertetabelle. +
+
![]() |
+
+
+ +Anders als in Listing 43.3 wird +in diesem Beispiel als erstes Argument von invoke +nicht das Objekt übergeben, an dem die Methode aufgerufen werden +soll, sondern der Wert null. +Das liegt daran, dass wir eine statische Methode aufrufen, +die keiner Objektinstanz, sondern dem Klassenobjekt zugeordnet ist. +Bei nicht-statischen Methoden ist die Übergabe von null +natürlich nicht erlaubt und würde zu einer NullPointerException +führen. |
+
+
|
+![]() |
+
+Die Ausgabe des Programms ist:
+
+
+Wertetabelle fuer times2
+ 0.0 -> 0.0
+ 1.0 -> 2.0
+ 2.0 -> 4.0
+ 3.0 -> 6.0
+ 4.0 -> 8.0
+ 5.0 -> 10.0
+Wertetabelle fuer java.lang.Math.exp
+ 0.0 -> 1.0
+ 1.0 -> 2.7182818284590455
+ 2.0 -> 7.38905609893065
+ 3.0 -> 20.085536923187668
+ 4.0 -> 54.598150033144236
+ 5.0 -> 148.4131591025766
+Wertetabelle fuer sqr
+ 0.0 -> 0.0
+ 1.0 -> 1.0
+ 2.0 -> 4.0
+ 3.0 -> 9.0
+ 4.0 -> 16.0
+ 5.0 -> 25.0
+Wertetabelle fuer java.lang.Math.sqrt
+ 0.0 -> 0.0
+ 1.0 -> 1.0
+ 2.0 -> 1.4142135623730951
+ 3.0 -> 1.7320508075688772
+ 4.0 -> 2.0
+ 5.0 -> 2.23606797749979
+
+
+
+
![]() |
+
+
+ +Der in Listing 43.5 vorgestellte +Code ist eigentlich nicht zur Nachahmung empfohlen, sondern soll nur +als Beispiel für den Aufruf parametrisierter Methoden mit Hilfe +des Reflection-APIs dienen. Ein zum hier vorgestellten Code äquivalentes +Beispiel auf der Basis von Interfaces wurde in Abschnitt 9.4.3 +vorgestellt. |
+
+
|
+![]() |
+
+Die Methode newInstance +der Klasse Class +ruft immer den parameterlosen Konstruktor auf, um ein Objekt zu instanzieren. +Mit Reflection ist es aber auch möglich, parametrisierte Konstruktoren +zur dynamischen Instanzierung zu verwenden. Dazu besitzt Class +zwei Methoden getConstructors +und getConstructor, +die dazu verwendet werden, Konstruktorenobjekte zu beschaffen. Anders +als getMethods +und getMethod +liefern sie allerdings kein Objekt des Typs Method, +sondern eines des Typs Constructor +zurück. + +
+Auch dieses besitzt die oben beschriebenen Methoden getModifiers, +getName +und getParameterTypes. +Der Aufruf einer Methode erfolgt allerdings nicht mit invoke, +sondern mit newInstance: +
+
+
++Object newInstance(Object[] initargs) ++ + |
++java.lang.reflect.Constructor | +
+newInstance +erwartet ebenfalls ein Array von Argumenten des Typs Object. +Diese werden gegebenenfalls in der zuvor beschriebenen Weise auf primitive +Typen abgebildet und rufen schließlich den passenden Konstruktor +auf. Als Rückgabewert von newInstance +wird das neu instanzierte Objekt geliefert. + +
+Das folgende Listing zeigt die Verwendung parametrisierter Konstruktoren +mit dem Reflection-API: + + +
+
+
+
+001 /* Listing4306.java */
+002
+003 import java.lang.reflect.*;
+004
+005 public class Listing4306
+006 {
+007 public static void main(String[] args)
+008 {
+009 Class clazz = TestConstructors.class;
+010 //Formale Parameter definieren
+011 Class[] formparas = new Class[2];
+012 formparas[0] = String.class;
+013 formparas[1] = String.class;
+014 try {
+015 Constructor cons = clazz.getConstructor(formparas);
+016 //Aktuelle Argumente definieren
+017 Object[] actargs = new Object[] {"eins", "zwei"};
+018 Object obj = cons.newInstance(actargs);
+019 ((TestConstructors)obj).print();
+020 } catch (Exception e) {
+021 System.err.println(e.toString());
+022 System.exit(1);
+023 }
+024 }
+025 }
+026
+027 class TestConstructors
+028 {
+029 private String arg1;
+030 private String arg2;
+031
+032 public TestConstructors()
+033 {
+034 arg1 = "leer";
+035 arg2 = "leer";
+036 }
+037
+038 public TestConstructors(String arg1)
+039 {
+040 this();
+041 this.arg1 = arg1;
+042 }
+043
+044 public TestConstructors(String arg1, String arg2)
+045 {
+046 this();
+047 this.arg1 = arg1;
+048 this.arg2 = arg2;
+049 }
+050
+051 public void print()
+052 {
+053 System.out.println("arg1 = " + arg1);
+054 System.out.println("arg2 = " + arg2);
+055 }
+056 }
+
+ |
++Listing4306.java | +
+Das Programm erzeugt zunächst ein Klassenobjekt zu der Klasse
+TestConstructors. Anschließend
+wird ein Array mit zwei Klassenobjekten der Klasse String
+erzeugt und als Spezifikation der formalen Parameter an getConstructor
+übergeben. Das zurückgegebene Constructor-Objekt
+wird dann mit zwei aktuellen Argumenten »eins« und »zwei«
+vom Typ String
+ausgestattet, die an seine Methode newInstance
+übergeben werden. Diese instanziert das Objekt, castet es auf
+die Klasse TestConstructors
+und ruft deren Methode print
+auf. Die Ausgabe des Programms ist:
+
+
+arg1 = eins
+arg2 = zwei
+
+
+
| Titel + | Inhalt + | Suchen + | Index + | DOC + | Handbuch der Java-Programmierung, 5. Auflage, Addison +Wesley, Version 5.0.1 + |
| << + | < + | > + | >> + | API + | © 1998, 2007 Guido Krüger & Thomas +Stark, http://www.javabuch.de + |