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 onlyGli 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 manuallyPer un metodo statico, il target viene ignorato — passa null:
Method parse = Integer.class.getMethod("parseInt", String.class);
Object n = parse.invoke(null, "42"); // → Integer 42Gli 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 chiamatosetAccessible(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()(Classcancellato) em.getGenericReturnType()(Type, mantiene i generics). - Parametri:
m.getParameterTypes(),m.getParameterCount()em.getParameters()(nomi disponibili se compilato con-parameters). - Varargs: un parametro
String...è in realtàString[]. Cercalo congetMethod("f", String[].class)e invocalo passando un vero array, oppure affidati al fatto cheinvokeaccetta 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.
Cosa ricavare dall'esecuzione:
- La factory statica è stata chiamata con
factory.invoke(null)— per un metodostaticl'oggetto target è irrilevante, quindinullè la convenzione. Il dispatcher ha poi riutilizzato lo stesso meccanismoinvokeper i metodi di istanza, passando il verocalccome target. Un'unica API, entrambi i tipi di metodo. divide(1, 0)non ha lanciatoArithmeticExceptiondirettamente dainvoke. Ha lanciatoInvocationTargetException, e la veraArithmeticException: / by zeroè stata trovata tramitegetCause(). Ogni framework che chiama codice utente per reflection deve svolgere questo meccanismo; dimenticarlo è il motivo per cui a volte si vede unaInvocationTargetExceptionconfusa in uno stack trace al posto del vero errore.- La ricerca di
addcon parametriInteger.classè fallita conNoSuchMethodExceptionanche seadd(int,int)esiste. La reflection confronta i tipi dei parametri esattamente senza boxing o allargamento —int.classeInteger.classsono chiavi diverse. Questo è il bug di reflection più comune e il motivo per cui i letterali primitivi.classsono importanti. - Il metodo
private secretera invocabile solo dopogetDeclaredMethod+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
Objecte sono stati castati al punto di chiamata ((Calculator) factory.invoke(...)), mentre l'intdiaddè tornato autoboxato comeInteger. La reflection non ha conoscenza statica dei tipi restituiti, quindi è responsabilità del chiamante eseguire il cast/unbox — e un cast errato emerge comeClassCastExceptiona runtime, non a compile time.