- PVSM.RU - https://www.pvsm.ru -
В этом туториале мы рассмотрим важный API, представленный в Java 7 и расширенный в новых версиях, java.lang.invoke.MethodHandles [1].

Мы узнаем, что такое method handles, как их создавать и использовать.
В документации API method handle имеет такое определение:
Method handle — это типизированная, исполняемая ссылка на базовый метод, конструктор, поле или другую низкоуровневую операцию с дополнительными трансформациями аргументов или возвращаемых значений.
Другими словами, method handles — это низкоуровневый механизм для поиска, адаптации и вызова методов. Объекты method handles неизменяемые и не имеют отображаемого состояния.
Для создания и использования MethodHandle нужно выполнить 4 действия:
Method handles были представлены для функционирования наряду с java.lang.reflect API [2], т.к. они созданы для разных целей и отличаются по своим характеристикам.
С точки зрения производительности, MethodHandles API может оказаться намного быстрее Reflection API, поскольку проверки доступа выполняются во время создания, а не исполнения. При наличии security manager’а это различие увеличивается, т.к. поиск классов и получение их элементов подвергаются дополнительным проверкам.
Однако, производительность — не единственный показатель оптимальности задачи, нужно учитывать, что MethodHandles API сложнее в использовании из-за недостатка таких механизмов, как получение методов класса, проверка маркеров доступа и др.
Несмотря на это, MethodHandles API дает возможность каррировать методы, менять тип и порядок параметров.
Теперь, зная определение и предназначение MethodHandles API, можем работать с ними. Начнем с поиска методов.
Первое, что нужно сделать, когда мы хотим создать method handle, — это получить lookup, объект-фабрику, отвечающий за создание method handles для методов, конструкторов и полей, видимых для класса lookup.
С помощью MethodHandles API можно создать lookup-объект с разными режимами доступа.
Создадим lookup, предоставляющий доступ к public-методам:
MethodHandles.Lookup publicLookup = MethodHandles.publicLookup();
Однако, если нам нужен доступ к методам private и protected, вместо этого мы можем использовать метод lookup():
MethodHandles.Lookup lookup = MethodHandles.lookup();
Для создания MethodHandle lookup-объекту необходимо задать тип, и это можно сделать с помощью класса MethodType.
В частности, MethodType представляет аргументы и тип возвращаемого значения, принимаемые и возвращаемые method handle, или передаваемые и ожидаемые вызывающим кодом.
Структура MethodType проста, она формируется возвращаемым типом вместе с соответствующим числом типов параметра, которые должны полностью соотноситься между method handle и вызывающим кодом.
Так же, как и MethodHandle, все экземпляры MethodType неизменяемы.
Посмотрим, как определить MethodType, задающий класс java.util.List в качестве типа возвращаемого значения и массив Object в качестве типа ввода данных:
MethodType mt = MethodType.methodType(List.class, Object[].class);
В случае, если метод возвращает простой или void тип значения, мы используем класс, представляющий эти типы (void.class, int.class …).
Определим MethodType, который возвращает значение int и принимает Object:
MethodType mt = MethodType.methodType(int.class, Object.class);
Можно приступать к созданию MethodHandle.
После того, как мы задали тип метода, для создания MethodHandle нужно найти его с помощью объекта lookup или publicLookup, который также выдает исходный класс и имя метода.
Lookup предоставляет набор методов, позволяющий находить method handle оптимальным способом с учетом области видимости метода. Рассмотрим основные подходы, начиная с простейших.
С помощью метода findVirtual() можно создать MethodHandle для метода экземпляра. Создадим его на основе метода concat() класса String:
MethodType mt = MethodType.methodType(String.class, String.class);
MethodHandle concatMH = publicLookup.findVirtual(String.class, "concat", mt);
Для получения доступа к статическому методу можно использовать метод findStatic():
MethodType mt = MethodType.methodType(List.class, Object[].class);
MethodHandle asListMH = publicLookup.findStatic(Arrays.class, "asList", mt);
В данном случае мы создали method handle метода, преобразующего массив типа Object в список List.
Получить доступ к конструктору можно с помощью метода findConstructor().
Создадим method handle с поведением, как у конструктора класса Integer с параметром String:
MethodType mt = MethodType.methodType(void.class, String.class);
MethodHandle newIntegerMH = publicLookup.findConstructor(Integer.class, mt);
С помощью method handle можно также получить доступ к полям.
Начнем с определения класса Book:
public class Book {
String id;
String title;
// constructor
}
В качестве исходного условия мы имеем прямую видимость между method handle и объявленным свойством, таким образом, можно создать method handle с поведением как у get-метода:
MethodHandle getTitleMH = lookup.findGetter(Book.class, "title", String.class);
Более подробную информацию об управлении переменными/полями ищите в статье Java 9 Variable Handles Demystified [3], где мы рассказываем о java.lang.invoke.VarHandle API [4], введенном в Java 9.
Создать method handle для метода типа private можно с помощью java.lang.reflect API [2].
Начнем с того, что создадим private метод для класса Book:
private String formatBook() {
return id + " > " + title;
}
Теперь мы можем создать method handle с поведением метода formatBook():
Method formatBookMethod = Book.class.getDeclaredMethod("formatBook");
formatBookMethod.setAccessible(true);
MethodHandle formatBookMH = lookup.unreflect(formatBookMethod);
Как только мы создали наш method handle, приступаем к следующему шагу. Класс MethodHandle дает нам 3 разных способа вызова method handle: invoke(), invokeWithArugments() и invokeExact().
Начнем со способа invoke.
При использовании метода invoke() количество аргументов (arity) фиксируется, но при этом возможно выполнение приведения типов и упаковка/распаковка аргументов и типов возвращаемого значения.
Теперь посмотрим, как можно использовать invoke() с упакованным аргументом:
MethodType mt = MethodType.methodType(String.class, char.class, char.class);
MethodHandle replaceMH = publicLookup.findVirtual(String.class, "replace", mt);
String output = (String) replaceMH.invoke("jovo", Character.valueOf('o'), 'a');
assertEquals("java", output);
В данном случае replaceMH требуются аргументы char, но метод invoke() распаковывает аргумент Character до его исполнения.
Вызов method handle с помощью метода invokeWithArguments имеет меньше всего ограничений.
По сути, помимо проверки типов и упаковки/распаковки аргументов и возвращаемых значений, он позволяет делать вызовы с переменным числом параметров.
На практике мы можем создать список Integer, имея массив значений int неизвестной длины:
MethodType mt = MethodType.methodType(List.class, Object[].class);
MethodHandle asList = publicLookup.findStatic(Arrays.class, "asList", mt);
List<Integer> list = (List<Integer>) asList.invokeWithArguments(1, 2);
assertThat(Arrays.asList(1,2), is(list));
Если нам необходимо, чтобы method handle выполнялся более ограниченно (по набору аргументов и их типу), мы используем метод invokeExact().
Фактически, он не предоставляет возможность приведения типов класса и требует фиксированного набора аргументов.
Посмотрим, как можно выполнить сложение двух значений int с помощью method handle:
MethodType mt = MethodType.methodType(int.class, int.class, int.class);
MethodHandle sumMH = lookup.findStatic(Integer.class, "sum", mt);
int sum = (int) sumMH.invokeExact(1, 11);
assertEquals(12, sum);
В данном случае, если передать в метод invokeExact число, не являющееся int, при вызове мы получим WrongMethodTypeException.
MethodHandles могут работать не только с полями и объектами, но и с массивами. При помощи asSpreader() API можно создать method handle, поддерживающий массивы в качестве позиционных аргументов.
В этом случае method handle принимает массив, распределяя его элементы как позиционные аргументы, и опционально — длину массива.
Посмотрим, как получить method handle, чтобы проверить, являются ли аргументы массива одинаковыми строками:
MethodType mt = MethodType.methodType(boolean.class, Object.class);
MethodHandle equals = publicLookup.findVirtual(String.class, "equals", mt);
MethodHandle methodHandle = equals.asSpreader(Object[].class, 2);
assertTrue((boolean) methodHandle.invoke(new Object[] { "java", "java" }));
Как только method handle задан, можно уточнить его, привязав к аргументу, без вызова метода.
Например, в Java 9 этот трюк используется для оптимизации конкатенации строк.
Посмотрим, как можно выполнить конкатенацию, привязав суффикс к concatMH:
MethodType mt = MethodType.methodType(String.class, String.class);
MethodHandle concatMH = publicLookup.findVirtual(String.class, "concat", mt);
MethodHandle bindedConcatMH = concatMH.bindTo("Hello ");
assertEquals("Hello World!", bindedConcatMH.invoke("World!"));
В Java 9 было внесено несколько изменений в MethodHandles API, чтобы упростить их использование.
Обновления касаются 3 основных аспектов:
loop, whileLoop, doWhileLoop, ...) и улучшенное управление исключениями с помощью tryFinally.Эти изменения повлекли за собой другие полезные нововведения:
Более подробный список изменений доступен в MethodHandles API Javadoc [5].
В этой статье мы познакомились с MethodHandles API, а также узнали, что из себя представляют Method Handles и как их использовать.
Мы также описали, как он связан с Reflection API. Так как вызов method handles это довольно низкоуровневая операция, их использование оправдано только в том случае, если они в точности подходят под ваши задачи.
Как обычно, весь исходный код для статьи доступен на Github [6].
Автор: jreznot
Источник [7]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/java/301029
Ссылки в тексте:
[1] java.lang.invoke.MethodHandles: https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/invoke/MethodHandles.html
[2] java.lang.reflect API: https://docs.oracle.com/javase/9/docs/api/java/lang/reflect/package-summary.html
[3] Java 9 Variable Handles Demystified: https://www.baeldung.com/java-variable-handles
[4] java.lang.invoke.VarHandle API: https://docs.oracle.com/javase/9/docs/api/java/lang/invoke/VarHandle.html
[5] MethodHandles API Javadoc: https://docs.oracle.com/javase/9/docs/api/java/lang/invoke/MethodHandles.html
[6] доступен на Github: https://github.com/eugenp/tutorials/tree/master/core-java-9
[7] Источник: https://habr.com/post/431922/?utm_campaign=431922
Нажмите здесь для печати.