1. введение
В этой статье мы рассмотрим важный API, который был представлен в Java 7 и улучшен в следующих версиях, а именно java.lang.invoke.MethodHandles .
В частности, мы узнаем, что такое дескрипторы методов, как их создавать и как их использовать.
2. Что Такое Дескрипторы Методов?
Переходя к его определению, как указано в документации по API:
Дескриптор метода-это типизированная, непосредственно исполняемая ссылка на базовый метод, конструктор, поле или аналогичную низкоуровневую операцию с необязательными преобразованиями аргументов или возвращаемых значений.
Проще говоря, дескрипторы методов-это низкоуровневый механизм для поиска, адаптации и вызова методов .
Дескрипторы методов неизменяемы и не имеют видимого состояния.
Для создания и использования MethodHandle требуется 4 шага:
- Создание поиска
- Создание типа метода
- Поиск дескриптора метода
- Вызов дескриптора метода
2.1. Методы обработки и отражения
Дескрипторы методов были введены для того, чтобы работать вместе с существующим java.lang.reflect API, так как они служат разным целям и имеют разные характеристики.
С точки зрения производительности метод Обрабатывает API может быть намного быстрее, чем API отражения, поскольку проверка доступа выполняется во время создания, а не во время выполнения . Это различие усиливается, если присутствует менеджер безопасности, поскольку поиск членов и классов подвергается дополнительным проверкам.
Однако, учитывая, что производительность не является единственной мерой пригодности для задачи, мы также должны учитывать, что MethodHandles API сложнее использовать из-за отсутствия таких механизмов, как перечисление классов-членов, проверка флагов доступности и многое другое.
Тем не менее, метод Обрабатывает API предлагает возможность каррировать методы, изменять типы параметров и изменять их порядок.
Имея четкое определение и цели дескрипторов метода API, теперь мы можем начать работать с ними, начиная с поиска.
3. Создание поиска
Первое, что нужно сделать, когда мы хотим создать дескриптор метода, – это получить поиск, объект фабрики, который отвечает за создание дескрипторов методов для методов, конструкторов и полей, которые видны классу поиска.
С помощью Метод обрабатывает API, можно создать объект поиска, с различными режимами доступа.
Давайте создадим поиск, который предоставляет доступ к public методам:
MethodHandles.Lookup publicLookup = MethodHandles.publicLookup();
Однако, если мы хотим иметь доступ также к private и protected методам, мы можем использовать вместо этого метод lookup() :
MethodHandles.Lookup lookup = MethodHandles.lookup();
4. Создание типа метода
Чтобы иметь возможность создать MethodHandle , объекту поиска требуется определение его типа, и это достигается с помощью класса MethodType .
В частности, a Тип метода представляет аргументы и тип возвращаемого значения, принятые и возвращенные дескриптором метода или переданные и ожидаемые вызывающим дескриптором метода .
Структура типа метода проста и формируется типом возвращаемого значения вместе с соответствующим количеством типов параметров, которые должны быть должным образом согласованы между дескриптором метода и всеми его вызывающими элементами.
Точно так же , как MethodHandle , даже экземпляры типа метода являются неизменяемыми.
Давайте посмотрим, как можно определить тип метода , который определяет java.util.Список класс в качестве возвращаемого типа и Объект массив в качестве входного типа:
MethodType mt = MethodType.methodType(List.class, Object[].class);
В случае, если метод возвращает примитивный тип или void в качестве возвращаемого типа, мы будем использовать класс, представляющий эти типы (void.class, int.class …).
Давайте определим тип метода , который возвращает значение int и принимает Объект :
MethodType mt = MethodType.methodType(int.class, Object.class);
Теперь мы можем перейти к созданию MethodHandle .
5. Поиск метода
После того, как мы определили тип нашего метода, чтобы создать MethodHandle, мы должны найти его через объект lookup или publicLookup , указав также исходный класс и имя метода.
В частности, фабрика поиска предоставляет набор методов , которые позволяют нам найти дескриптор метода соответствующим образом, учитывая область применения нашего метода. Начиная с самого простого сценария, давайте рассмотрим основные из них.
5.1. Дескриптор метода для методов
Использование метода find Virtual() позволяет нам создать MethodHandle для метода объекта. Давайте создадим его на основе метода concat() класса String :
MethodType mt = MethodType.methodType(String.class, String.class); MethodHandle concatMH = publicLookup.findVirtual(String.class, "concat", mt);
5.2. Дескриптор метода для статических методов
Когда мы хотим получить доступ к статическому методу, мы можем вместо этого использовать метод find Static() :
MethodType mt = MethodType.methodType(List.class, Object[].class); MethodHandle asListMH = publicLookup.findStatic(Arrays.class, "asList", mt);
В этом случае мы создали дескриптор метода, который преобразует массив Объектов в Список из них.
5.3. Дескриптор метода для конструкторов
Получить доступ к конструктору можно с помощью метода find Constructor () .
Давайте создадим дескрипторы методов, которые ведут себя как конструктор класса Integer , принимая атрибут String :
MethodType mt = MethodType.methodType(void.class, String.class); MethodHandle newIntegerMH = publicLookup.findConstructor(Integer.class, mt);
5.4. Дескриптор метода для полей
Используя дескриптор метода, можно получить доступ также к полям.
Давайте начнем определять класс Book :
public class Book { String id; String title; // constructor }
Имея в качестве предварительного условия видимость прямого доступа между дескриптором метода и объявленным свойством, мы можем создать дескриптор метода, который ведет себя как геттер:
MethodHandle getTitleMH = lookup.findGetter(Book.class, "title", String.class);
Для получения дополнительной информации об обработке переменных/полей взгляните на дескрипторы переменных Java 9 , демистифицированные , где мы обсуждаем java.lang.invoke.VarHandle API, добавленный в Java 9.
5.5. Дескриптор метода для частных методов
Создание дескриптора метода для частного метода можно выполнить с помощью API java.lang.reflect .
Давайте начнем добавлять метод private в класс Book :
private String formatBook() { return id + " > " + title; }
Теперь мы можем создать дескриптор метода, который ведет себя точно так же, как метод format Book() :
Method formatBookMethod = Book.class.getDeclaredMethod("formatBook"); formatBookMethod.setAccessible(true); MethodHandle formatBookMH = lookup.unreflect(formatBookMethod);
6. Вызов дескриптора метода
После того, как мы создали наши дескрипторы методов, их использование-это следующий шаг. В частности, класс MethodHandle предоставляет 3 различных способа выполнения дескриптора метода: invoke(), invoke С аргументами() и invokeExact() .
Давайте начнем с опции invoke .
6.1. Вызов дескриптора метода
При использовании метода 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);
В этом случае для замены требуются аргументы char , но invoke() выполняет распаковку аргумента Character перед его выполнением.
6.2. Обращение С Аргументами
Вызов дескриптора метода с помощью метода invokeWithArguments является наименее ограничительным из трех вариантов.
Фактически, он позволяет вызывать переменную arity в дополнение к приведению и упаковке/распаковке аргументов и возвращаемых типов.
Переходя к практике, это позволяет нам создать Список из целого числа , начиная с массива значений |/int :
MethodType mt = MethodType.methodType(List.class, Object[].class); MethodHandle asList = publicLookup.findStatic(Arrays.class, "asList", mt); Listlist = (List ) asList.invokeWithArguments(1,2); assertThat(Arrays.asList(1,2), is(list));
6.3. Вызов точного
В случае, если мы хотим быть более строгими в том, как мы выполняем дескриптор метода (количество аргументов и их тип), мы должны использовать метод invokeExact () .
На самом деле, он не обеспечивает никакого приведения к предоставленному классу и требует фиксированного количества аргументов.
Давайте посмотрим, как мы можем sum two int значения с помощью дескриптора метода:
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.
7. Работа С Массивом
Дескрипторы методов предназначены не только для работы с полями или объектами, но и с массивами. На самом деле, с помощью API asSpreader() можно создать метод распространения массива.
В этом случае дескриптор метода принимает аргумент массива, распространяя его элементы в качестве позиционных аргументов и, возможно, длину массива.
Давайте посмотрим, как мы можем распространить метод 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" }));
8. Улучшение дескриптора метода
Как только мы определили дескриптор метода, его можно улучшить, привязав дескриптор метода к аргументу, фактически не вызывая его.
Например, в Java 9 такое поведение используется для оптимизации String конкатенации.
Давайте посмотрим, как мы можем выполнить конкатенацию, привязав суффикс к нашему 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!"));
9. Усовершенствования Java 9
В Java 9 было внесено несколько улучшений в Method Handles API с целью сделать его намного проще в использовании.
Усовершенствования затронули 3 основные темы:
- Функции поиска – разрешение поиска классов из разных контекстов и поддержка неабстрактных методов в интерфейсах
- Обработка аргументов – улучшение функций сворачивания аргументов, сбора аргументов и распространения аргументов
- Дополнительные комбинации – добавление циклов ( loop , whileLoop, doWhileLoop… ) и лучшая поддержка обработки исключений с помощью tryFinally
Эти изменения привели к небольшим дополнительным преимуществам:
- Повышенная оптимизация компилятора JVM
- Сокращение количества экземпляров
- Включена точность в использовании дескрипторов метода API
Подробная информация о сделанных улучшениях доступна в MethodHandles API Javadoc .
10. Заключение
В этой статье мы рассмотрели дескрипторы метода API, что это такое и как мы можем их использовать.
Мы также обсудили, как это связано с API отражения, и поскольку дескрипторы методов допускают низкоуровневые операции, лучше избегать их использования, если они не полностью соответствуют объему задания.
Как всегда, полный исходный код этой статьи доступен на Github .