W3docs

Java Reflection : Appeler des méthodes

Inspectez et appelez des méthodes par réflexion en Java avec la classe Method.

Un objet Method décrit une méthode et — point crucial — vous permet de l'appeler : method.invoke(target, args...). C'est le cœur réflexif des lanceurs de tests (trouver les méthodes @Test, les invoquer), des frameworks qui distribuent les requêtes vers des gestionnaires par leur nom, et des ponts de scripting. Ce chapitre couvre la recherche de méthodes, la correspondance des types de paramètres qui surprend tout le monde, l'invocation de méthodes d'instance et statiques, les valeurs de retour, et la façon dont les exceptions sont encapsulées.

Si vous débutez avec la réflexion, commencez par l'introduction à la réflexion et le chapitre sur l'objet Class, car tout ici part d'un Class<?>. La lecture et l'écriture des membres de données fonctionnent de la même façon et sont couvertes dans la réflexion sur les champs.

Trouver des méthodes

Vous recherchez une méthode par nom et types de paramètres — les types de paramètres permettent à Java de distinguer les surcharges :

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

Les objets Class des paramètres doivent correspondre exactement aux types de paramètres déclarés — il n'y a pas de résolution de surcharge ni d'élargissement. getMethod("add", Integer.class, Integer.class) ne trouvera pas add(int, int) ; vous devez passer int.class. Une mauvaise combinaison lève une NoSuchMethodException. Pour une méthode sans argument, ne passez aucun argument de classe : getMethod("toString").

Invoquer : instance, statique et arguments

invoke prend l'objet cible en premier, puis les arguments sous forme de 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

Pour une méthode statique, la cible est ignorée — passez null :

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

Les arguments primitifs sont autoboxés dans le tableau Object[] ; le runtime les déboxe pour correspondre aux paramètres primitifs. La valeur de retour est toujours un Object — les primitifs reviennent boxés, les méthodes void retournent null.

Comment les exceptions remontent : InvocationTargetException

C'est le piège le plus important. Si la méthode invoquée lève une exception, invoke ne propage pas cette exception directement. Elle l'encapsule dans une InvocationTargetException, et vous récupérez la vraie exception avec getCause() :

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

Les autres exceptions vérifiées concernent l'appel réflexif lui-même, pas le corps de la méthode :

  • IllegalAccessException — la méthode est inaccessible et vous n'avez pas appelé setAccessible(true).
  • IllegalArgumentException — nombre ou types d'arguments incorrects, ou type de cible incorrect.
  • NoSuchMethodException — levée au moment de la recherche, pas au moment de l'invocation.

Ainsi : les échecs de recherche et les erreurs d'arguments sont levés « directement », mais tout ce que le code de la méthode lance est mis en bouteille dans InvocationTargetException.

Types de retour, varargs et génériques

  • Métadonnées du type de retour : m.getReturnType() (Class effacée) et m.getGenericReturnType() (Type, conserve les génériques).
  • Paramètres : m.getParameterTypes(), m.getParameterCount(), et m.getParameters() (noms disponibles si compilé avec -parameters).
  • Varargs : un paramètre String... est en réalité String[]. Recherchez-le avec getMethod("f", String[].class) et invoquez en passant un tableau réel, ou comptez sur le fait que invoke acceptera un tableau final pour le slot varargs.
  • Méthodes bridge/synthétiques : les classes génériques génèrent des méthodes bridge cachées ; filtrez-les avec m.isBridge() / m.isSynthetic() lors de l'énumération.

Un exemple complet : un mini-distributeur de commandes

Le programme construit un petit distributeur qui associe des commandes en chaîne à des méthodes d'un objet service, les invoque par réflexion avec des arguments analysés, gère une méthode qui lève une exception (pour montrer le déballage de InvocationTargetException), et appelle une fabrique static avec une cible null.

java— editable, runs on the server

Ce qu'il faut retenir de l'exécution :

  • La fabrique statique a été appelée avec factory.invoke(null) — pour une méthode static, l'objet cible n'a pas d'importance, donc null est la convention. Le distributeur a ensuite réutilisé le même mécanisme invoke pour les méthodes d'instance, en passant le vrai calc comme cible. Une seule API, les deux types de méthodes.
  • divide(1, 0) n'a pas levé ArithmeticException depuis invoke. Il a levé InvocationTargetException, et la vraie ArithmeticException: / by zero a été trouvée via getCause(). Tout framework qui appelle du code utilisateur par réflexion doit déencapsuler cela ; oublier de le faire est la raison pour laquelle vous voyez parfois une InvocationTargetException déroutante dans une trace de pile au lieu de la vraie erreur.
  • La recherche de add avec des paramètres Integer.class a échoué avec NoSuchMethodException alors que add(int,int) existe. La réflexion fait correspondre les types de paramètres exactement sans boxing ni élargissement — int.class et Integer.class sont des clés différentes. C'est le bug de réflexion le plus courant et la raison pour laquelle les littéraux primitifs .class sont importants.
  • La méthode private secret n'était invocable qu'après getDeclaredMethod + setAccessible(true). Comme pour les champs, la variante de recherche (getDeclared…) et la porte d'accès (setAccessible) sont deux étapes indépendantes ; vous avez besoin des deux pour atteindre un membre privé.
  • Les valeurs de retour sont arrivées sous forme d'Object et ont été castées au site d'appel ((Calculator) factory.invoke(...)), tandis que l'int de add est revenu autoboxé en Integer. La réflexion n'a aucune connaissance statique des types de retour, donc l'appelant est responsable du cast/unbox — et un mauvais cast se manifeste comme une ClassCastException à l'exécution, pas à la compilation.

Entraînement

Pratique
Vous invoquez une méthode par réflexion avec 'm.invoke(obj)', et le corps de la méthode lève une 'IllegalStateException'. Dans votre code appelant, quelle exception attrapez-vous réellement, et comment accédez-vous à l''IllegalStateException' ?
Vous invoquez une méthode par réflexion avec 'm.invoke(obj)', et le corps de la méthode lève une 'IllegalStateException'. Dans votre code appelant, quelle exception attrapez-vous réellement, et comment accédez-vous à l''IllegalStateException' ?
Was this page helpful?