Titel | Inhalt | Suchen | Index | DOC | Handbuch der Java-Programmierung, 3. Auflage
<< | < | > | >> | API | Kapitel 43 - Reflection

## 43.3 Methoden- und Konstruktorenaufrufe

- 43.3 Methoden- und Konstruktorenaufrufe
  - 43.3.1 Parameterlose Methoden
  - 43.3.2 Parametrisierte Methoden
  - 43.3.3 Parametrisierte Konstruktoren

### 43.3.1 Parameterlose Methoden

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 plazieren:

    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 }

Listing 43.2: Testcode in der main-Methode

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, daß 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:

- Das Testing wird durch ein Rahmenprogramm `Test` ausgelöst, das als Argument den Namen der zu testenden Klasse erwartet.
- Die zu testende Klasse soll automatisch gefunden, geladen und mit dem Default-Konstruktor instanziert werden.
- In der Klasse mit dem Testcode sollen automatisch alle öffentlichen, nicht-statischen Methoden ausgeführt werden, deren Name mit "test" beginnt.

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, daß 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. |  |
Tip

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 muß 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 plazieren.

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

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 muß mit "test" anfangen
    043       String name = methods[i].getName();
    044       if (!name.startsWith("test")) {
    045         continue;
    046       }
    047       //Methode muß 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

Listing 43.3: Die Klasse Test

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, daß keine Parameter zu übergeben sind.

|  | Beachten Sie, daß 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. |  |
Hinweis

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 }

Listing 43.4: Die Klasse TestQueue

Ein Aufruf von

    java Test Queue

würde nun ein neues Objekt des Typs `TestQueue` instanzieren und nacheinander die Methoden `test1`, `test2` usw. aufrufen.

### 43.3.2 Parametrisierte Methoden

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

Listing 43.5: Funktionszeiger mit Reflection nachbilden

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, daß bereits der Compiler überprüfen kann, ob die genannte Klasse vorhanden ist. |  |
Tip

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:
    formparas[0] = (new Double(0)).getClass();
oder
    formparas[0] = Double.class;
Tip

Auch eine parametrisierte Methode kann mit `invoke` aufgerufen werden. Im Unterschied zur parameterlosen muß 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, daß 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. |  |
Hinweis

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

### 43.3.3 Parametrisierte Konstruktoren

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

Listing 43.6: Parametrisierte Konstruktoren mit Reflection aufrufen

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, 3. Auflage, Addison Wesley, Version 3.0
<< | < | > | >> | API | © 1998-2002 Guido Krüger, http://www.javabuch.de