24.5 Methoden aufrufen 

Nach dem Abfragen und Setzen von Variablenwerten und Konstruktor-Aufrufen zum Erzeugen eines Objekts ist das Aufrufen von Methoden per Reflection der letzte Schritt. Wenn zur Compile-Zeit der Name der Methode nicht feststeht, lässt sich zur Laufzeit dennoch eine im Programm definierte Methode aufrufen, wenn ihr Name als Zeichenkette vorliegt.
Zunächst gehen wir wieder von einem Class-Objekt aus, das 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 Methode 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, ruft invoke() die Zielmethode auf – im Englischen heißt dies dynamic invocation. invoke() erwartet zwei Argumente: ein Array mit Argumenten, die der aufgerufenen Methode übergeben werden, und eine Referenz auf das Objekt, auf dem die Methode aufgerufen werden soll und zur Auflösung der dynamischen Bindung dient.
final class java.lang.reflect.Method
extends AccessibleObject
implements GenericDeclaration, Member |
- Object invoke( Object obj, Object... args ) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException Ruft eine Methode des Objekts obj mit den gegebenen Argumenten auf. Wie schon beim Konstruktor löst die Methode eine InvocationTargetException aus, wenn die aufzurufende Methode eine Exception auslöst.
Beispiel Wir erzeugen ein Point-Objekt und setzen im Konstruktor den x-Wert auf 10. Anschließend fragen wir mit der Methode getX(), die wir dynamisch aufrufen, den x-Wert wieder ab. Listing 24.17 com/tutego/insel/meta/InvokeMethod.java, main() Point p = new Point( 10, 0 );
Method method = p.getClass().getMethod( "getX" );
String returnType = method.getReturnType().getName();
Object returnValue = method.invoke( p );
System.out.printf( "(%s) %s", returnType, returnValue ); // (double) 10.0 |
Beispiele der Varargs sind bei getMethod() die Parametertypen und bei invoke() die Argumente für setLocation(). Da getMethod() eine beliebige Anzahl von Argumenten annehmen kann und kein Argument dazuzählt, muss die Methode nicht so parametrisiert werden:
Method method = p.getClass().getMethod( "getX", (Class[]) null );
Auffälliger ist die Möglichkeit der variablen Argumentanzahl bei invoke(). Da ein Getter keine Parameter besitzt, heißt es kurz method.invoke(p); statt wie vor Java 5:
method.invoke( p, (Object[]) null );
Interessant sind Methoden mit Parameterliste, wie setLocation():
Point p = new Point(); Method method = p.getClass().getMethod( "setLocation", int.class, int.class ); method.invoke( p, 1, 2 ); System.out.println( p );
24.5.1 Statische Methoden aufrufen 

Wir wollen ein Beispiel programmieren, in dem die Klasse InvokeMain die main()-Funktion einer anderen Klasse, HasMain, mit einem Parameter aufruft.
Listing 24.18 com/tutego/insel/meta/InvokeMain.java
package com.tutego.insel.meta; import java.lang.reflect.*; import java.util.Arrays; public class InvokeMain { public static void main( String[] args ) throws Exception { String[] argv = { "-option", "Parameter" }; Method method = Class.forName( "com.tutego.insel.meta.HasMain" ). getMethod( "main", argv.getClass() ); method.invoke( null, new Object[]{ argv } ); } } class HasMain { public static void main( String[] args ) { System.out.println( "Got: " + Arrays.toString( args ) ); } }
24.5.2 Dynamische Methodenaufrufe bei festen Methoden beschleunigen 

Werden über Reflection Methoden aufgerufen, deren Methodennamen erst zur Laufzeit bestimmt werden, so verlieren wir die Typsicherheit vom Compiler, und die Geschwindigkeit ist nicht optimal – auch wenn es seit Java 5 nicht mehr so schlimm ist. (Doch immer noch zeigt eine einfache Zeitmessung einen deutlichen Unterschied.) 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 von Aufruf beschleunigen können. Ein möglicher Weg hierbei ist, in Kenntnis des Namens der Methode – nennen wir die Methode meth() – den Namen in einer (abstrakten) Oberklasse dem Compiler bereits bekannt zu machen. Reflection ist nur zum Laden einer Unterklasse mit gegebenem Namen nötig; die normale, dynamische Methodenbindung erledigt den Rest – ganz ohne versteckte Schnüre, doppelte Böden oder Spiegel (Reflection).
Versuchen wir, den folgenden Code nach diesem Schema zu ändern:
Listing 24.19 com/tutego/insel/meta/DynamReflection.java, main()
Class<?> clazz = Class.forName("com.tutego.insel.meta.DynamReflectionMethod" ); Object o = clazz.newInstance(); clazz.getMethod( "meth" ).invoke( o );
DynamReflection ist die Hauptklasse, die die Klasse DynamReflectionMethod über Class. forName() anfordert.
Listing 24.20 com/tutego/insel/meta/DynamReflectionMethod.java
package com.tutego.insel.meta; public class DynamReflectionMethod { public void meth() { System.out.println( "Bewusste Raucher trinken Filterkaffee" ); } }
Über das Class-Objekt erzeugt newInstance() ein neues Exemplar. getMethod() sucht die Beschreibung der Methode meth() heraus, und invoke() ruft die Methode »meth« auf. Hier genau liegt ein kleiner Geschwindigkeitsverlust. Wenn es uns gelänge, um das invoke() herumzukommen, wäre das schon ein großer Fortschritt. Dies schaffen wir, indem wir eine Schnittstelle (oder Oberklasse) für DynamReflectionMethod konstruieren, die genau diese Methode vorschreibt. Die implementierende Klasse (beziehungsweise Unterklasse) wird dann eine Implementierung angeben.
Listing 24.21 com/tutego/insel/meta/DynamAbstract.java
package com.tutego.insel.meta; interface DynamBase { void meth(); } class DynamBaseMethod implements DynamBase { public void meth() { System.out.println( "Bewusste Raucher trinken Filterkaffee" ); } } public class DynamAbstract { public static void main( String[] args ) throws Exception { Class<?> clazz = Class.forName( "DynamBaseMethod" ); DynamBase o = (DynamBase) clazz.newInstance(); o.meth(); } }
DynamBase ist eine Schnittstelle, die zur Übersetzungszeit bekannt ist. Die virtuelle Maschine löst den Aufruf nach den üblichen Regeln der dynamischen Bindung selbst auf. Die Klasse DynamBaseMethod wird ebenfalls 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 – wenn wir die Möglichkeit, den Klassennamen durch einen String anzugeben, außer Acht lassen –, aber mit der höheren Geschwindigkeit eines konventionellen Methodenaufrufs ohne Reflection.