Java Reflection: Invocação de Métodos
Inspecione e invoque métodos via reflection em Java com a classe Method.
Um objeto Method descreve um método e — crucialmente — permite chamá-lo: method.invoke(target, args...). Este é o coração reflexivo dos executores de testes (encontrar métodos @Test e invocá-los), de frameworks que despacham para handlers por nome e de bridges de scripting. Este capítulo abrange a busca de métodos, a correspondência de tipos de parâmetros que confunde a todos, a invocação de métodos de instância e estáticos, os valores de retorno e como as exceções são encapsuladas.
Se você é novo em reflection, comece com a introdução ao reflection e o capítulo sobre o objeto de classe, pois tudo aqui começa a partir de um Class<?>. A leitura e escrita de membros de dados funciona da mesma forma e é abordada em reflection em campos.
Encontrando métodos
Você pesquisa um método pelo nome mais os tipos dos parâmetros — os tipos de parâmetros são como o Java distingue 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 onlyOs objetos Class dos parâmetros devem corresponder aos tipos de parâmetros declarados exatamente — não há resolução de sobrecarga nem alargamento. getMethod("add", Integer.class, Integer.class) não encontrará add(int, int); você deve passar int.class. Uma combinação incorreta lança NoSuchMethodException. Para um método sem argumentos, não passe argumentos de classe: getMethod("toString").
Invocando: instância, estático e argumentos
invoke recebe o objeto alvo primeiro, depois os argumentos como um 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 um método estático, o alvo é ignorado — passe null:
Method parse = Integer.class.getMethod("parseInt", String.class);
Object n = parse.invoke(null, "42"); // → Integer 42Os argumentos primitivos são autoboxed no Object[]; o runtime os desencapsula para corresponder aos parâmetros primitivos. O valor de retorno é sempre Object — os primitivos voltam encapsulados, e métodos void retornam null.
Como as exceções surgem: InvocationTargetException
Este é o principal ponto de atenção. Se o método invocado lançar uma exceção, invoke não propaga essa exceção diretamente. Ela é encapsulada em um InvocationTargetException, e você recupera a real com getCause():
try {
riskyMethod.invoke(target);
} catch (InvocationTargetException e) {
Throwable real = e.getCause(); // the exception the method actually threw
// handle 'real', not 'e'
}As outras exceções verificadas dizem respeito à chamada de reflection em si, não ao corpo do método:
IllegalAccessException— o método é inacessível e você não chamousetAccessible(true).IllegalArgumentException— número ou tipos de argumentos errados, ou tipo de alvo incorreto.NoSuchMethodException— lançada no momento da busca, não no momento da invocação.
Portanto: falhas de busca e erros de argumento lançam "diretamente", mas qualquer coisa que o próprio código do método lançar fica encapsulada dentro de InvocationTargetException.
Tipos de retorno, varargs e generics
- Metadados do tipo de retorno:
m.getReturnType()(Classapagado) em.getGenericReturnType()(Type, mantém generics). - Parâmetros:
m.getParameterTypes(),m.getParameterCount()em.getParameters()(nomes disponíveis se compilado com-parameters). - Varargs: um parâmetro
String...é na verdadeString[]. Pesquise-o comgetMethod("f", String[].class)e invoque passando um array real, ou confie no fato de queinvokeaceitará um array final para o slot varargs. - Métodos bridge/sintéticos: classes genéricas geram métodos bridge ocultos; filtre-os com
m.isBridge()/m.isSynthetic()ao enumerar.
Um exemplo prático: um mini dispatcher de comandos
O programa constrói um pequeno dispatcher que mapeia comandos em string para métodos em um objeto de serviço, os invoca reflexivamente com argumentos parseados, lida com um método que lança exceção (para mostrar o desencapsulamento do InvocationTargetException) e chama uma factory static com um alvo null.
O que extrair da execução:
- A factory estática foi chamada com
factory.invoke(null)— para um métodostatic, o objeto alvo é irrelevante, portantonullé a convenção. O dispatcher então reutilizou o mesmo mecanismoinvokepara métodos de instância, passando ocalcreal como alvo. Uma API, dois tipos de método. divide(1, 0)não lançouArithmeticExceptiondiretamente deinvoke. Ele lançouInvocationTargetException, e aArithmeticException: / by zerogenuína foi encontrada viagetCause(). Todo framework que chama código do usuário reflexivamente precisa desencapsular isso; esquecer é o motivo pelo qual às vezes você vê um confusoInvocationTargetExceptionem um stack trace em vez do erro real.- Pesquisar
addcom parâmetrosInteger.classfalhou comNoSuchMethodExceptionmesmo comadd(int,int)existindo. A reflection corresponde aos tipos de parâmetros exatamente, sem boxing ou alargamento —int.classeInteger.classsão chaves diferentes. Este é o bug de reflection mais comum e o motivo pelo qual os literais.classprimitivos importam. - O método
private secretsó pôde ser invocado apósgetDeclaredMethod+setAccessible(true). Assim como com campos, a variante de busca (getDeclared…) e a porta de acesso (setAccessible) são duas etapas independentes; você precisa de ambas para acessar um membro privado. - Os valores de retorno chegaram como
Objecte foram convertidos no local de chamada ((Calculator) factory.invoke(...)), enquanto ointdeaddvoltou autoboxed comoInteger. A reflection não tem conhecimento estático dos tipos de retorno, portanto o chamador é responsável pela conversão — e uma conversão errada se manifesta comoClassCastExceptionem tempo de execução, não em tempo de compilação.