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 onlyLos 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 manuallyPara 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 42Los 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 llamastesetAccessible(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()(Classborrado) ym.getGenericReturnType()(Type, conserva los genéricos). - Parámetros:
m.getParameterTypes(),m.getParameterCount(), ym.getParameters()(nombres disponibles si se compila con-parameters). - Varargs: un parámetro
String...es realmenteString[]. Búscalo congetMethod("f", String[].class)e invócalo pasando un array real, o confía en queinvokeaceptará 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.
Lo que se puede observar en la ejecución:
- El factory estático fue llamado con
factory.invoke(null)— para un métodostaticel objeto destino es irrelevante, por lo quenulles la convención. El despachador luego reutilizó el mismo mecanismoinvokepara métodos de instancia, pasando elcalcreal como destino. Una sola API, dos tipos de métodos. divide(1, 0)no lanzóArithmeticExceptionfuera deinvoke. LanzóInvocationTargetException, y laArithmeticException: / by zerogenuina se encontró mediantegetCause(). 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 unaInvocationTargetExceptionconfusa en un stack trace en lugar del error real.- La búsqueda de
addcon parámetrosInteger.classfalló conNoSuchMethodExceptionaunque existeadd(int,int). Reflection compara los tipos de parámetros exactamente, sin boxing ni ampliación —int.classeInteger.classson claves diferentes. Este es el error de reflection más común y la razón por la que importan los literales.classprimitivos. - El método
private secretsolo fue invocable después degetDeclaredMethod+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
Objecty se castearon en el sitio de llamada ((Calculator) factory.invoke(...)), mientras que elintdeaddregresó autoboxeado comoInteger. 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 comoClassCastExceptionen tiempo de ejecución, no en tiempo de compilación.