W3docs

Java Reflection: Invocare i Metodi

Ispeziona e invoca metodi per reflection in Java con la classe Method.

Un oggetto Method descrive un metodo e — in modo cruciale — permette di chiamarlo: method.invoke(target, args...). Questo è il cuore della reflection nei test runner (trova i metodi @Test, invocali), nei framework che smistano le richieste verso handler tramite nome e nei bridge di scripting. Questo capitolo tratta come trovare i metodi, la corrispondenza dei tipi dei parametri che inganna tutti, l'invocazione di metodi di istanza e statici, i valori restituiti e il modo in cui le eccezioni vengono incapsulate.

Se sei nuovo alla reflection, inizia con l'introduzione alla reflection e il capitolo sull'oggetto class, poiché tutto qui parte da un Class<?>. La lettura e la scrittura dei membri dati funziona nello stesso modo ed è trattata in reflection sui campi.

Trovare i metodi

Cerchi un metodo tramite nome più tipi dei parametri — i tipi dei parametri sono il modo in cui Java distingue gli overload:

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 only

Gli oggetti Class dei parametri devono corrispondere ai tipi dei parametri dichiarati esattamente — non c'è risoluzione degli overload né allargamento. getMethod("add", Integer.class, Integer.class) non troverà add(int, int); devi passare int.class. Una combinazione errata lancia NoSuchMethodException. Per un metodo senza argomenti, non passare argomenti di classe: getMethod("toString").

Invocazione: istanza, statico e argomenti

invoke riceve prima l'oggetto target, poi gli argomenti come 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 manually

Per un metodo statico, il target viene ignorato — passa null:

Method parse = Integer.class.getMethod("parseInt", String.class);
Object n = parse.invoke(null, "42");          // → Integer 42

Gli argomenti primitivi vengono autoboxati nell'Object[]; il runtime li unboxa per corrispondere ai parametri primitivi. Il valore restituito è sempre Object — i primitivi tornano boxati, i metodi void restituiscono null.

Come le eccezioni emergono: InvocationTargetException

Questo è l'errore più importante da ricordare. Se il metodo invocato lancia un'eccezione, invoke non propaga quell'eccezione direttamente. La incapsula in una InvocationTargetException e la recuperi con getCause():

try {
  riskyMethod.invoke(target);
} catch (InvocationTargetException e) {
  Throwable real = e.getCause();    // the exception the method actually threw
  // handle 'real', not 'e'
}

Le altre eccezioni checked riguardano la chiamata reflection stessa, non il corpo del metodo:

  • IllegalAccessException — il metodo è inaccessibile e non hai chiamato setAccessible(true).
  • IllegalArgumentException — numero o tipi di argomenti errati, oppure tipo di target errato.
  • NoSuchMethodException — lanciata al momento della ricerca, non al momento dell'invocazione.

Quindi: i fallimenti di ricerca e gli errori sugli argomenti vengono lanciati "direttamente", ma qualsiasi cosa lanci il codice del metodo viene racchiusa dentro InvocationTargetException.

Tipi restituiti, varargs e generics

  • Metadati del tipo restituito: m.getReturnType() (Class cancellato) e m.getGenericReturnType() (Type, mantiene i generics).
  • Parametri: m.getParameterTypes(), m.getParameterCount() e m.getParameters() (nomi disponibili se compilato con -parameters).
  • Varargs: un parametro String... è in realtà String[]. Cercalo con getMethod("f", String[].class) e invocalo passando un vero array, oppure affidati al fatto che invoke accetta un array finale per lo slot varargs.
  • Metodi bridge/sintetici: le classi generiche generano metodi bridge nascosti; filtrali con m.isBridge() / m.isSynthetic() durante l'enumerazione.

Un esempio pratico: un mini dispatcher di comandi

Il programma costruisce un piccolo dispatcher che mappa comandi stringa su metodi di un oggetto service, li invoca per reflection con argomenti analizzati, gestisce un metodo che lancia (per mostrare lo svolgimento di InvocationTargetException) e chiama una factory static con un target null.

java— editable, runs on the server

Cosa ricavare dall'esecuzione:

  • La factory statica è stata chiamata con factory.invoke(null) — per un metodo static l'oggetto target è irrilevante, quindi null è la convenzione. Il dispatcher ha poi riutilizzato lo stesso meccanismo invoke per i metodi di istanza, passando il vero calc come target. Un'unica API, entrambi i tipi di metodo.
  • divide(1, 0) non ha lanciato ArithmeticException direttamente da invoke. Ha lanciato InvocationTargetException, e la vera ArithmeticException: / by zero è stata trovata tramite getCause(). Ogni framework che chiama codice utente per reflection deve svolgere questo meccanismo; dimenticarlo è il motivo per cui a volte si vede una InvocationTargetException confusa in uno stack trace al posto del vero errore.
  • La ricerca di add con parametri Integer.class è fallita con NoSuchMethodException anche se add(int,int) esiste. La reflection confronta i tipi dei parametri esattamente senza boxing o allargamento — int.class e Integer.class sono chiavi diverse. Questo è il bug di reflection più comune e il motivo per cui i letterali primitivi .class sono importanti.
  • Il metodo private secret era invocabile solo dopo getDeclaredMethod + setAccessible(true). Come per i campi, il tipo di ricerca (getDeclared…) e il cancello di accesso (setAccessible) sono due passaggi indipendenti; servono entrambi per raggiungere un membro privato.
  • I valori restituiti sono arrivati come Object e sono stati castati al punto di chiamata ((Calculator) factory.invoke(...)), mentre l'int di add è tornato autoboxato come Integer. La reflection non ha conoscenza statica dei tipi restituiti, quindi è responsabilità del chiamante eseguire il cast/unbox — e un cast errato emerge come ClassCastException a runtime, non a compile time.

Esercizio

Pratica
Invochi un metodo per reflection con 'm.invoke(obj)', e il corpo del metodo lancia una 'IllegalStateException'. Nel tuo codice chiamante, quale eccezione catturi effettivamente e come raggiungi la 'IllegalStateException'?
Invochi un metodo per reflection con 'm.invoke(obj)', e il corpo del metodo lancia una 'IllegalStateException'. Nel tuo codice chiamante, quale eccezione catturi effettivamente e come raggiungi la 'IllegalStateException'?
Was this page helpful?