W3docs

Java Reflection: Invocar métodos

Inspecciona e invoca métodos de forma reflectiva en Java con la clase Method.

Un objeto Method describe un método y, lo que es crucial, permite invocarlo: method.invoke(target, args...). Este es el núcleo reflectivo de los ejecutores de pruebas (buscar métodos @Test, invocarlos), de los frameworks que despachan hacia manejadores por nombre, y de los puentes de scripting. Este capítulo cubre cómo encontrar métodos, la coincidencia de tipos de parámetros que confunde a todos, cómo invocar métodos de instancia y estáticos, los valores de retorno y cómo se encapsulan las excepciones.

Si eres nuevo en reflection, comienza con la introducción a reflection y el capítulo sobre el objeto class, ya que todo aquí parte de un Class<?>. Leer y escribir miembros de datos funciona de la misma manera y se cubre en reflection sobre campos.

Cómo encontrar métodos

Un método se busca por nombre más tipos de parámetros — los tipos de parámetros son la forma en que Java distingue las sobrecargas:

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

Los objetos Class de los parámetros deben coincidir exactamente con los tipos de parámetros declarados — no hay resolución de sobrecargas ni ampliación de tipos. getMethod("add", Integer.class, Integer.class) no encontrará add(int, int); debes pasar int.class. Una combinación incorrecta lanza NoSuchMethodException. Para un método sin argumentos, no pases argumentos de clase: getMethod("toString").

Invocar: instancia, estático y argumentos

invoke recibe el objeto destino primero, luego los argumentos como un 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

Para un método estático, el destino se ignora — pasa null:

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

Los argumentos primitivos se autoboxean en el Object[]; el runtime los desboxea para coincidir con los parámetros primitivos. El valor de retorno siempre es Object — los primitivos vuelven boxeados, los métodos void devuelven null.

Cómo emergen las excepciones: InvocationTargetException

Este es el aspecto más importante a tener en cuenta. Si el método invocado lanza una excepción, invoke no propaga esa excepción directamente. La envuelve en una InvocationTargetException, y se recupera la real con getCause():

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

Las otras excepciones verificadas se refieren a la llamada de reflection en sí, no al cuerpo del método:

  • IllegalAccessException — el método es inaccesible y no llamaste setAccessible(true).
  • IllegalArgumentException — número o tipos de argumentos incorrectos, o tipo de destino incorrecto.
  • NoSuchMethodException — se lanza en el momento de la búsqueda, no en el momento de la invocación.

Entonces: los fallos de búsqueda y los errores de argumentos se lanzan "directamente", pero cualquier cosa que lance el propio código del método queda encapsulada dentro de InvocationTargetException.

Tipos de retorno, varargs y genéricos

  • Metadatos del tipo de retorno: m.getReturnType() (Class borrado) y m.getGenericReturnType() (Type, conserva los genéricos).
  • Parámetros: m.getParameterTypes(), m.getParameterCount(), y m.getParameters() (nombres disponibles si se compila con -parameters).
  • Varargs: un parámetro String... es realmente String[]. Búscalo con getMethod("f", String[].class) e invócalo pasando un array real, o confía en que invoke aceptará un array final para el slot de varargs.
  • Métodos bridge/sintéticos: las clases genéricas generan métodos bridge ocultos; fíltralos con m.isBridge() / m.isSynthetic() al enumerar.

Un ejemplo completo: un mini despachador de comandos

El programa construye un pequeño despachador que asigna comandos de string a métodos en un objeto de servicio, los invoca de forma reflectiva con argumentos procesados, maneja un método que lanza una excepción (para mostrar el desencapsulado de InvocationTargetException) y llama a un factory static con un destino null.

java— editable, runs on the server

Lo que se puede observar en la ejecución:

  • El factory estático fue llamado con factory.invoke(null) — para un método static el objeto destino es irrelevante, por lo que null es la convención. El despachador luego reutilizó el mismo mecanismo invoke para métodos de instancia, pasando el calc real como destino. Una sola API, dos tipos de métodos.
  • divide(1, 0) no lanzó ArithmeticException fuera de invoke. Lanzó InvocationTargetException, y la ArithmeticException: / by zero genuina se encontró mediante getCause(). Todo framework que llama código de usuario de forma reflectiva tiene que desencapsular esto; olvidarlo es la razón por la que a veces ves una InvocationTargetException confusa en un stack trace en lugar del error real.
  • La búsqueda de add con parámetros Integer.class falló con NoSuchMethodException aunque existe add(int,int). Reflection compara los tipos de parámetros exactamente, sin boxing ni ampliación — int.class e Integer.class son claves diferentes. Este es el error de reflection más común y la razón por la que importan los literales .class primitivos.
  • El método private secret solo fue invocable después de getDeclaredMethod + setAccessible(true). Al igual que con los campos, el tipo de búsqueda (getDeclared…) y la puerta de acceso (setAccessible) son dos pasos independientes; necesitas ambos para acceder a un miembro privado.
  • Los valores de retorno llegaron como Object y se castearon en el sitio de llamada ((Calculator) factory.invoke(...)), mientras que el int de add regresó autoboxeado como Integer. Reflection no tiene conocimiento estático de los tipos de retorno, por lo que el llamador es responsable del cast/unbox — y un cast incorrecto se manifiesta como ClassCastException en tiempo de ejecución, no en tiempo de compilación.

Práctica

Práctica
Invocas un método de forma reflectiva con 'm.invoke(obj)', y el cuerpo del método lanza una 'IllegalStateException'. En tu código de llamada, ¿qué excepción capturas realmente y cómo accedes a la 'IllegalStateException'?
Invocas un método de forma reflectiva con 'm.invoke(obj)', y el cuerpo del método lanza una 'IllegalStateException'. En tu código de llamada, ¿qué excepción capturas realmente y cómo accedes a la 'IllegalStateException'?
Was this page helpful?