21.4 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, 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.
Hier klicken, um das Bild zu Vergrößern
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.
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 21.13 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
21.4.1 Statische Methoden aufrufen
Wir wollen ein Beispiel programmieren, in dem die Klasse InvokeMain die main()-Funktion mit dem Parameter einer anderen Klasse HasMain aufruft.
Listing 21.14 InvokeMain.java
import java.lang.reflect.*;
public class InvokeMain
{
public static void main( String args[] ) throws Exception
{
String nargs[] = new String[] { "-option", "Parameter" };
Method methode = Class.forName( "HasMain" ).getMethod( "main" ,
new Class[] {nargs.getClass()}) ;
methode.invoke( null, new Object[]{nargs} );
}
}
class HasMain
{
public static void main( String args[] )
{
System.out.println( "Hier bin ich." );
}
}
21.4.2 Dynamische Methodenaufrufe bei festen Methoden beschleunigen
Werden über Reflection Methoden aufgerufen, deren Funktionsnamen erst zur Laufzeit bestimmt werden, so ist die Geschwindigkeit niedrig - auch wenn es seit dem Java SDK 1.4 nicht mehr so schlimm ist. (Doch immer noch zeigt eine einfache Zeitmessung einen Unterschied um den Faktor 1.000 bei dem Java 2 SDK 1.4.1 unter Windows.) 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. Ein möglicher Weg hierbei ist, bei Wissen über den Namen der Methode - nennen wir sie meth() - ihn in einer (abstrakten) Oberklasse dem Compiler bereits 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 21.15 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 21.16 DynamReflectionMethod.java
public class DynamReflectionMethod
{
public void meth()
{
System.out.println("Bewusste Raucher trinken Filterkaffee" );
}
}
DynamReflection ist nun die Hauptklasse, die die Klasse DynamReflectionMethod erst dann lädt, wenn Class.forName() aufgerufen wird. Über das Class-Objekt erzeugen wir dann mit newInstance() ein neues Exemplar. Anschließend sucht getMethod() die Beschreibung der Methode meth() heraus. Mit invoke() rufen wir diese anschließend auf.
Hier genau liegt der Geschwindigkeitsverlust. Wenn es uns gelingen würde, 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 dann genau diese Methode vorschreibt. Die implementierte Klasse (beziehungsweise Unterklasse) wird dann eine Implementierung angeben.
Listing 21.17 DynamAbstract.java
interface DynamBase
{
void meth();
}
class DynamBaseMethod implements DynamBase
{
public void meth()
{
System.out.println( "Bewusste Raucher trinken Filterkaffee" );
}
}
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 wird nun den Aufruf selbst nach den üblichen Regeln für die dynamische Bindung auflösen und umsetzen. 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, jetzt mal außer Acht lassen -, aber mit der höheren Geschwindigkeit eines konventionellen Methodenaufrufs ohne Reflection.
|