В этом туториале мы рассмотрим важный API, представленный в Java 7 и расширенный в новых версиях, java.lang.invoke.MethodHandles. Мы узнаем, что такое method handles, как их создавать и использовать. В документации API method handle имеет такое определение: Другими словами, method handles — это низкоуровневый механизм для поиска, адаптации и вызова методов. Объекты method handles неизменяемые и не имеют отображаемого состояния. Для создания и использования MethodHandle нужно выполнить 4 действия: Method handles были представлены для функционирования наряду с java.lang.reflect API, т.к. они созданы для разных целей и отличаются по своим характеристикам. С точки зрения производительности, MethodHandles API может оказаться намного быстрее Reflection API, поскольку проверки доступа выполняются во время создания, а не исполнения. При наличии security manager’а это различие увеличивается, т.к. поиск классов и получение их элементов подвергаются дополнительным проверкам. Однако, производительность — не единственный показатель оптимальности задачи, нужно учитывать, что MethodHandles API сложнее в использовании из-за недостатка таких механизмов, как получение методов класса, проверка маркеров доступа и др.  Несмотря на это, MethodHandles API дает возможность каррировать методы, менять тип и порядок параметров. Теперь, зная определение и предназначение MethodHandles API, можем работать с ними. Начнем с поиска методов. Первое, что нужно сделать, когда мы хотим создать method handle, — это получить lookup, объект-фабрику, отвечающий за создание method handles для методов, конструкторов и полей, видимых для класса lookup. С помощью MethodHandles API можно создать lookup-объект с разными режимами доступа. Создадим lookup, предоставляющий доступ к public-методам: Однако, если нам нужен доступ к методам private и protected, вместо этого мы можем использовать метод lookup(): Для создания MethodHandle lookup-объекту необходимо задать тип, и это можно сделать с помощью класса MethodType. В частности, MethodType представляет аргументы и тип возвращаемого значения, принимаемые и возвращаемые method handle, или передаваемые и ожидаемые вызывающим кодом. Структура MethodType проста, она формируется возвращаемым типом вместе с соответствующим числом типов параметра, которые должны полностью соотноситься между method handle и вызывающим кодом. Так же, как и MethodHandle, все экземпляры MethodType неизменяемы. Посмотрим, как определить MethodType, задающий класс java.util.List в качестве типа возвращаемого значения и массив Object в качестве типа ввода данных: В случае, если метод возвращает простой или void тип значения, мы используем класс, представляющий эти типы (void.class, int.class …). Определим MethodType, который возвращает значение int и принимает Object: Можно приступать к созданию MethodHandle. После того, как мы задали тип метода, для создания MethodHandle нужно найти его с помощью объекта lookup или publicLookup, который также выдает исходный класс и имя метода. Lookup предоставляет набор методов, позволяющий находить method handle оптимальным способом с учетом области видимости метода. Рассмотрим основные подходы, начиная с простейших. С помощью метода findVirtual() можно создать MethodHandle для метода экземпляра. Создадим его на основе метода concat() класса String: Для получения доступа к статическому методу можно использовать метод findStatic(): В данном случае мы создали method handle метода, преобразующего массив типа Object в список List. Получить доступ к конструктору можно с помощью метода findConstructor(). Создадим method handle с поведением, как у конструктора класса Integer с параметром String: С помощью method handle можно также получить доступ к полям. Начнем с определения класса Book: В качестве исходного условия мы имеем прямую видимость между method handle и объявленным свойством, таким образом, можно создать method handle с поведением как у get-метода: Более подробную информацию об управлении переменными/полями ищите в статье Java 9 Variable Handles Demystified, где мы рассказываем о java.lang.invoke.VarHandle API, введенном в Java 9. Создать method handle для метода типа private можно с помощью java.lang.reflect API.
Начнем с того, что создадим private метод для класса Book: Теперь мы можем создать method handle с поведением метода formatBook(): Как только мы создали наш method handle, приступаем к следующему шагу. Класс MethodHandle дает нам 3 разных способа вызова method handle: invoke(), invokeWithArugments() и invokeExact(). Начнем со способа invoke. При использовании метода invoke() количество аргументов (arity) фиксируется, но при этом возможно выполнение приведения типов и упаковка/распаковка аргументов и типов возвращаемого значения. Теперь посмотрим, как можно использовать invoke() с упакованным аргументом: В данном случае replaceMH требуются аргументы char, но метод invoke() распаковывает аргумент Character до его исполнения. Вызов method handle с помощью метода invokeWithArguments имеет меньше всего ограничений. По сути, помимо проверки типов и упаковки/распаковки аргументов и возвращаемых значений, он позволяет делать вызовы с переменным числом параметров. На практике мы можем создать список Integer, имея массив значений int неизвестной длины: Если нам необходимо, чтобы method handle выполнялся более ограниченно (по набору аргументов и их типу), мы используем метод invokeExact(). Фактически, он не предоставляет возможность приведения типов класса и требует фиксированного набора аргументов. Посмотрим, как можно выполнить сложение двух значений int с помощью method handle: В данном случае, если передать в метод invokeExact число, не являющееся int, при вызове мы получим WrongMethodTypeException. MethodHandles могут работать не только с полями и объектами, но и с массивами. При помощи asSpreader() API можно создать method handle, поддерживающий массивы в качестве позиционных аргументов. В этом случае method handle принимает массив, распределяя его элементы как позиционные аргументы, и опционально — длину массива. Посмотрим, как получить method handle, чтобы проверить, являются ли аргументы массива одинаковыми строками: Как только method handle задан, можно уточнить его, привязав к аргументу, без вызова метода. Например, в Java 9 этот трюк используется для оптимизации конкатенации строк. Посмотрим, как можно выполнить конкатенацию, привязав суффикс к concatMH: В Java 9 было внесено несколько изменений в MethodHandles API, чтобы упростить их использование. Обновления касаются 3 основных аспектов: Эти изменения повлекли за собой другие полезные нововведения: Более подробный список изменений доступен в MethodHandles API Javadoc. В этой статье мы познакомились с MethodHandles API, а также узнали, что из себя представляют Method Handles и как их использовать. Мы также описали, как он связан с Reflection API. Так как вызов method handles это довольно низкоуровневая операция, их использование оправдано только в том случае, если они в точности подходят под ваши задачи. Как обычно, весь исходный код для статьи доступен на Github.