W3docs

Java Reflection: Вызов методов

Инспекция и рефлективный вызов методов Java с помощью класса Method.

Объект Method описывает один метод и — что важно — позволяет его вызвать: method.invoke(target, args...). Это рефлективное ядро тест-раннеров (поиск методов @Test и их вызов), фреймворков, диспетчеризующих обработчики по имени, и мостов для скриптинга. В этой главе рассматриваются: поиск методов, сопоставление типов параметров (с которым все спотыкаются), вызов методов экземпляра и статических методов, возвращаемые значения и оборачивание исключений.

Если вы новичок в рефлексии, начните с введения в рефлексию и главы об объекте класса, поскольку всё здесь начинается с Class<?>. Чтение и запись полей данных работает аналогично и рассмотрено в разделе рефлексия полей.

Поиск методов

Метод ищется по имени и типам параметров — именно по типам параметров Java различает перегрузки:

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

Объекты Class параметров должны точно совпадать с объявленными типами — никакого разрешения перегрузок и расширения нет. getMethod("add", Integer.class, Integer.class) не найдёт add(int, int); необходимо передать int.class. Неверная комбинация бросает NoSuchMethodException. Для метода без аргументов не передавайте классов: getMethod("toString").

Вызов: экземпляр, статический метод и аргументы

invoke принимает сначала целевой объект, затем аргументы в виде 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

Для статического метода целевой объект игнорируется — передайте null:

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

Примитивные аргументы автоматически упаковываются в Object[]; среда выполнения распаковывает их для соответствия примитивным параметрам. Возвращаемое значение всегда Object — примитивы возвращаются в упакованном виде, методы void возвращают null.

Как всплывают исключения: InvocationTargetException

Это самая важная ловушка. Если вызванный метод бросает исключение, invoke не распространяет его напрямую. Оно оборачивается в InvocationTargetException, а настоящее исключение извлекается через getCause():

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

Остальные проверяемые исключения относятся к самому вызову через рефлексию, а не к телу метода:

  • IllegalAccessException — метод недоступен и вы не вызвали setAccessible(true).
  • IllegalArgumentException — неверное количество/типы аргументов или неверный тип целевого объекта.
  • NoSuchMethodException — бросается во время поиска, не во время вызова.

Итак: ошибки поиска и неверные аргументы бросаются «напрямую», а всё, что бросает собственный код метода, упаковывается в InvocationTargetException.

Возвращаемые типы, varargs и дженерики

  • Метаданные возвращаемого типа: m.getReturnType() (стёртый Class) и m.getGenericReturnType() (Type, сохраняет дженерики).
  • Параметры: m.getParameterTypes(), m.getParameterCount() и m.getParameters() (имена доступны при компиляции с флагом -parameters).
  • Varargs: параметр String... на самом деле является String[]. Ищите его через getMethod("f", String[].class) и вызывайте, передавая реальный массив, либо полагайтесь на то, что invoke принимает завершающий массив для слота varargs.
  • Bridge/synthetic-методы: дженерик-классы генерируют скрытые bridge-методы; отфильтровывайте их с помощью m.isBridge() / m.isSynthetic() при перечислении.

Разбор примера: мини-диспетчер команд

Программа строит небольшой диспетчер, который отображает строковые команды на методы объекта-сервиса, вызывает их рефлективно с разобранными аргументами, обрабатывает метод, бросающий исключение (для демонстрации разворачивания InvocationTargetException), и вызывает статическую фабрику с целевым объектом null.

java— editable, runs on the server

Что следует вынести из запуска:

  • Статическая фабрика вызвана через factory.invoke(null) — для static-метода целевой объект не важен, поэтому null является принятым соглашением. Диспетчер затем использовал тот же механизм invoke для методов экземпляра, передавая реальный calc в качестве цели. Один API — для обоих видов методов.
  • divide(1, 0) не бросил ArithmeticException из invoke. Было брошено InvocationTargetException, а настоящий ArithmeticException: / by zero обнаружен через getCause(). Каждый фреймворк, вызывающий пользовательский код рефлективно, вынужден разворачивать это исключение; забывчивость — именно поэтому иногда вместо реальной ошибки в стектрейсе можно видеть непонятный InvocationTargetException.
  • Поиск add с параметрами Integer.class завершился ошибкой NoSuchMethodException, хотя add(int,int) существует. Рефлексия сопоставляет типы параметров точно, без автоупаковки и расширения — int.class и Integer.class — это разные ключи. Это самая распространённая ошибка при работе с рефлексией, и именно поэтому важны литералы .class примитивов.
  • Приватный метод secret удалось вызвать только после getDeclaredMethod + setAccessible(true). Как и с полями, выбор метода поиска (getDeclared…) и открытие доступа (setAccessible) — это два независимых шага; оба необходимы для доступа к приватному члену.
  • Возвращаемые значения поступали как Object и приводились в месте вызова ((Calculator) factory.invoke(...)), тогда как int из add вернулся в упакованном виде как Integer. Рефлексия не имеет статических знаний о возвращаемых типах, поэтому приведение/распаковка — ответственность вызывающей стороны, а неверное приведение проявится как ClassCastException во время выполнения, а не во время компиляции.

Практика

Практика
Вы вызываете метод рефлективно через 'm.invoke(obj)', и тело метода бросает 'IllegalStateException'. Какое исключение вы фактически поймаете в вызывающем коде и как добраться до 'IllegalStateException'?
Вы вызываете метод рефлективно через 'm.invoke(obj)', и тело метода бросает 'IllegalStateException'. Какое исключение вы фактически поймаете в вызывающем коде и как добраться до 'IllegalStateException'?
Was this page helpful?