Java Reflection: Methoden aufrufen
Methoden in Java reflektiv untersuchen und aufrufen mit der Method-Klasse – Parameter, Rückgabewerte und Ausnahmen.
Ein Method-Objekt beschreibt eine Methode und ermöglicht es – entscheidend – sie zu aufzurufen: method.invoke(target, args...). Das ist der reflektive Kern von Test-Frameworks (finde @Test-Methoden, rufe sie auf), von Frameworks, die Handler per Name aufrufen, und von Scripting-Brücken. Dieses Kapitel behandelt das Finden von Methoden, den Parameter-Typ-Abgleich, der alle verwirrt, das Aufrufen von Instanz- und statischen Methoden, Rückgabewerte und wie Ausnahmen verpackt werden.
Wenn Sie neu bei Reflection sind, beginnen Sie mit der Reflection-Einführung und dem Kapitel über das Klassenobjekt, da hier alles mit einem Class<?> beginnt. Das Lesen und Schreiben von Datenelementen funktioniert auf dieselbe Weise und wird im Kapitel über das Reflektieren auf Felder behandelt.
Methoden finden
Sie suchen eine Methode anhand von Name plus Parametertypen — die Parametertypen unterscheiden Überladungen in Java:
Class<?> c = Calc.class;
Method m1 = c.getMethod("add", int.class, int.class); // public, incl. inherited
Method m2 = c.getDeclaredMethod("secret", String.class); // any access, this class only
Method[] pub = c.getMethods(); // all public methods, incl. Object's and inherited
Method[] own = c.getDeclaredMethods(); // all access levels, declared here onlyDie Class-Objekte für Parameter müssen mit den deklarierten Parametertypen exakt übereinstimmen — es gibt keine Überladungsauflösung und keine Erweiterung. getMethod("add", Integer.class, Integer.class) findet add(int, int) nicht; Sie müssen int.class übergeben. Eine falsche Kombination wirft NoSuchMethodException. Für eine Methode ohne Argumente übergeben Sie keine Klassenargumente: getMethod("toString").
Aufrufen: Instanz, statisch und Argumente
invoke nimmt zuerst das Zielobjekt, dann die Argumente als Varargs Object[]:
Calc calc = new Calc();
Method add = Calc.class.getMethod("add", int.class, int.class);
Object result = add.invoke(calc, 2, 3); // → Integer 5 (autoboxed)
int sum = (int) result; // unbox manuallyBei einer statischen Methode wird das Ziel ignoriert — übergeben Sie null:
Method parse = Integer.class.getMethod("parseInt", String.class);
Object n = parse.invoke(null, "42"); // → Integer 42Primitive Argumente werden automatisch in das Object[] geboxt; die Laufzeit entboxt sie, um den primitiven Parametern zu entsprechen. Der Rückgabewert ist immer Object — Primitive kommen geboxt zurück, void-Methoden geben null zurück.
Wie Ausnahmen auftreten: InvocationTargetException
Das ist die wichtigste Falle überhaupt. Wenn die aufgerufene Methode eine Ausnahme wirft, propagiert invoke diese Ausnahme nicht direkt. Sie wird in einer InvocationTargetException verpackt, und Sie holen die echte Ausnahme mit getCause():
try {
riskyMethod.invoke(target);
} catch (InvocationTargetException e) {
Throwable real = e.getCause(); // the exception the method actually threw
// handle 'real', not 'e'
}Die anderen geprüften Ausnahmen betreffen den Reflection-Aufruf selbst, nicht den Methodenrumpf:
IllegalAccessException— die Methode ist nicht zugänglich und Sie habensetAccessible(true)nicht aufgerufen.IllegalArgumentException— falsche Anzahl/Typen von Argumenten oder falscher Zieltyp.NoSuchMethodException— wird zur Suchzeit geworfen, nicht zur Aufrufzeit.
Also: Suchfehler und Argumentfehler werden „direkt" geworfen, aber alles, was der eigene Code der Methode wirft, ist in InvocationTargetException verpackt.
Rückgabetypen, Varargs und Generics
- Rückgabetyp-Metadaten:
m.getReturnType()(ausgelöschteClass) undm.getGenericReturnType()(Type, behält Generics). - Parameter:
m.getParameterTypes(),m.getParameterCount()undm.getParameters()(Namen verfügbar wenn mit-parameterskompiliert). - Varargs: ein
String...-Parameter ist wirklichString[]. Suchen Sie ihn mitgetMethod("f", String[].class)und rufen Sie ihn durch Übergabe eines echten Arrays auf, oder verlassen Sie sich darauf, dassinvokeein abschließendes Array für den Varargs-Slot akzeptiert. - Bridge/synthetische Methoden: generische Klassen erzeugen versteckte Bridge-Methoden; filtern Sie diese beim Aufzählen mit
m.isBridge()/m.isSynthetic().
Ein ausgearbeitetes Beispiel: ein Mini-Befehls-Dispatcher
Das Programm baut einen kleinen Dispatcher, der String-Befehle auf Methoden eines Service-Objekts abbildet, diese reflektiv mit geparsten Argumenten aufruft, eine Methode behandelt, die wirft (um das InvocationTargetException-Entpacken zu zeigen), und eine static-Factory mit einem null-Ziel aufruft.
Was aus dem Lauf zu entnehmen ist:
- Die statische Factory wurde mit
factory.invoke(null)aufgerufen — bei einerstatic-Methode ist das Zielobjekt irrelevant, daher istnulldie Konvention. Der Dispatcher verwendete dann denselbeninvoke-Mechanismus für Instanzmethoden und übergab das echtecalcals Ziel. Eine API, beide Arten von Methoden. divide(1, 0)hat keineArithmeticExceptionausinvokegeworfen. Es warfInvocationTargetException, und die echteArithmeticException: / by zerowurde übergetCause()gefunden. Jedes Framework, das Benutzercode reflektiv aufruft, muss dies entpacken; wer es vergisst, sieht manchmal eine verwirrendeInvocationTargetExceptionim Stack-Trace statt des echten Fehlers.- Das Nachschlagen von
addmitInteger.class-Parametern scheiterte mitNoSuchMethodException, obwohladd(int,int)existiert. Reflection gleicht Parametertypen exakt ohne Boxing oder Erweiterung ab —int.classundInteger.classsind verschiedene Schlüssel. Das ist der häufigste Reflection-Fehler und der Grund, warum primitive.class-Literale wichtig sind. - Die
private secret-Methode war nur nachgetDeclaredMethod+setAccessible(true)aufrufbar. Wie bei Feldern sind der Lookup-Stil (getDeclared…) und das Zugangstor (setAccessible) zwei unabhängige Schritte; man braucht beide, um ein privates Element zu erreichen. - Rückgabewerte kamen als
Objectan und wurden an der Aufrufstelle gecastet ((Calculator) factory.invoke(...)), währendadd'sintautogeboxed alsIntegerzurückkam. Reflection hat kein statisches Wissen über Rückgabetypen, daher ist der Aufrufer für den Cast/Unbox verantwortlich — und ein falscher Cast erscheint zur Laufzeit alsClassCastException, nicht zur Kompilierzeit.