Дескрипторы метода в Java

Методы в Java

1. Вступление

В этой статье мы собираемся изучить важный API, который был представлен в Java 7 и улучшен в следующих версиях,java.lang.invoke.MethodHandles.

В частности, мы узнаем, что такое дескрипторы методов, как их создавать и как их использовать.

2. Что такое дескрипторы методов?

Приходя к его определению, как указано в документации API:

Дескриптор метода - это типизированная, непосредственно исполняемая ссылка на базовый метод, конструктор, поле или аналогичную низкоуровневую операцию с необязательными преобразованиями аргументов или возвращаемых значений.

Проще говоря,method handles are a low-level mechanism for finding, adapting and invoking methods.

Дескрипторы метода неизменны и не имеют видимого состояния.

Для создания и использованияMethodHandle требуется 4 шага:

  • Создание поиска

  • Создание типа метода

  • Нахождение дескриптора метода

  • Вызов дескриптора метода

2.1. Дескрипторы методов против отражения

Дескрипторы методов были введены для работы вместе с существующим APIjava.lang.reflect, поскольку они служат разным целям и имеют разные характеристики.

С точки зрения производительностиMethodHandles API can be much faster than the Reflection API since the access checks are made at creation time rather than at execution time. Эта разница усиливается, если присутствует менеджер безопасности, так как поиск членов и классов подвергается дополнительным проверкам.

Однако, учитывая, что производительность - не единственная мера пригодности для задачи, мы также должны учитывать, что APIMethodHandles труднее использовать из-за отсутствия таких механизмов, как перечисление классов членов, проверка флагов доступности и т. Д. .

Даже в этом случае APIMethodHandles предлагает возможность каррировать методы, изменять типы параметров и изменять их порядок.

Имея четкое определение и цели APIMethodHandles, теперь мы можем приступить к работе с ними, начиная с поиска.

3. СозданиеLookup

Первое, что нужно сделать, когда мы хотим создать дескриптор метода, - это получить поиск, фабричный объект, который отвечает за создание дескрипторов методов для методов, конструкторов и полей, которые видимы для класса поиска.

Через APIMethodHandles можно создать объект поиска с различными режимами доступа.

Давайте создадим поиск, который предоставляет доступ к методамpublic:

MethodHandles.Lookup publicLookup = MethodHandles.publicLookup();

Однако, если мы хотим иметь доступ также к методамprivate иprotected, мы можем использовать вместо этого методlookup():

MethodHandles.Lookup lookup = MethodHandles.lookup();

4. СозданиеMethodType

Чтобы иметь возможность создаватьMethodHandle, объекту поиска требуется определение его типа, и это достигается с помощью классаMethodType.

В частности,a MethodType represents the arguments and return type accepted and returned by a method handle or passed and expected by a method handle caller.

СтруктураMethodType проста и состоит из возвращаемого типа вместе с соответствующим количеством типов параметров, которые должны быть правильно сопоставлены между дескриптором метода и всеми его вызывающими объектами.

Так же, как и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.

5. НахождениеMethodHandle

После того, как мы определили тип нашего метода, чтобы создатьMethodHandle,, мы должны найти его через объектlookup илиpublicLookup, предоставив также исходный класс и имя метода.

В частности, фабрика поиска предоставляетset of methods, которые позволяют нам найти дескриптор метода соответствующим образом, учитывая область действия нашего метода. Начнем с простейшего сценария, давайте рассмотрим основные.

5.1. Дескриптор метода для методов

Использование методаfindVirtual() позволяет нам создать MethodHandle для метода объекта. Давайте создадим его на основе методаconcat() классаString:

MethodType mt = MethodType.methodType(String.class, String.class);
MethodHandle concatMH = publicLookup.findVirtual(String.class, "concat", mt);

5.2. Дескриптор метода для статических методов

Когда мы хотим получить доступ к статическому методу, мы можем вместо этого использовать методfindStatic():

MethodType mt = MethodType.methodType(List.class, Object[].class);

MethodHandle asListMH = publicLookup.findStatic(Arrays.class, "asList", mt);

В этом случае мы создали дескриптор метода, который преобразует массивObjects вList из них.

5.3. Дескриптор метода для конструкторов

Получить доступ к конструктору можно с помощью методаfindConstructor().

Давайте создадим дескрипторы методов, которые будут вести себя как конструктор класса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 Variable Handles Demystified, где мы обсуждаем APIjava.lang.invoke.VarHandle, добавленный в Java 9.

5.5. Дескриптор метода для частных методов

Создание описателя метода для частного метода может быть выполнено с помощью APIjava.lang.reflect.

Начнем добавлять методprivate к классуBook:

private String formatBook() {
    return id + " > " + title;
}

Теперь мы можем создать дескриптор метода, который ведет себя точно так же, как методformatBook():

Method formatBookMethod = Book.class.getDeclaredMethod("formatBook");
formatBookMethod.setAccessible(true);

MethodHandle formatBookMH = lookup.unreflect(formatBookMethod);

6. Вызов дескриптора метода

После того, как мы создали дескрипторы наших методов, следующим шагом будет их использование. В частности, классMethodHandle предоставляет 3 различных способа выполнения дескриптора метода:invoke(),invokeWithArugments() иinvokeExact().

Начнем с опцииinvoke.

6.1. Вызов дескриптора метода

При использовании методаinvoke() мы обеспечиваем фиксированное количество аргументов (арность), но разрешаем выполнение приведения и упаковки / распаковки аргументов и возвращаемых типов.

Давайте посмотрим, как можно использовать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 перед его выполнением.

6.2. Вызов с аргументами

Вызов дескриптора метода с использованием методаinvokeWithArguments является наименее ограничивающим из трех вариантов.

Фактически, он допускает вызов переменной arity, в дополнение к приведению и упаковке / распаковке аргументов и возвращаемых типов.

На практике это позволяет нам создатьList изInteger, начиная сarray значенийint:

MethodType mt = MethodType.methodType(List.class, Object[].class);
MethodHandle asList = publicLookup.findStatic(Arrays.class, "asList", mt);

List list = (List) asList.invokeWithArguments(1,2);

assertThat(Arrays.asList(1,2), is(list));

6.3. Вызов Exact

В случае, если мы хотим быть более строгими в способе выполнения дескриптора метода (количество аргументов и их тип), мы должны использовать методinvokeExact().

Фактически, он не обеспечивает никакого преобразования в предоставленный класс и требует фиксированного количества аргументов.

Давайте посмотрим, как мы можемsum два значения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. Работа с массивом

MethodHandles предназначены не только для работы с полями или объектами, но также и с массивами. Фактически, с помощью APIasSpreader() можно создать дескриптор метода распределения массива.

В этом случае дескриптор метода принимает аргумент массива, распространяя его элементы в качестве позиционных аргументов и, необязательно, длину массива.

Давайте посмотрим, как мы можем распространить дескриптор метода, чтобы проверить, равны ли элементы в массиве:

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 в APIMethodHandles было внесено несколько улучшений, чтобы упростить его использование.

Улучшения коснулись 3 основных тем:

  • Lookup functions - разрешает поиск классов из разных контекстов и поддерживает неабстрактные методы в интерфейсах

  • Argument handling - улучшение функций сворачивания аргументов, сбора аргументов и распределения аргументов

  • Additional combinations - добавление циклов (loop,whileLoop,doWhileLoop…) и улучшенная поддержка обработки исключений с помощьюtryFinally

Эти изменения привели к нескольким дополнительным преимуществам:

  • Увеличение оптимизаций компилятора JVM

  • Сокращение использования

  • Включена точность использования APIMethodHandles

Подробная информация о сделанных улучшениях доступна вMethodHandles API Javadoc.

10. Заключение

В этой статье мы рассмотрели APIMethodHandles, что это такое и как мы можем их использовать.

Мы также обсудили, как он относится к Reflection API, и поскольку дескрипторы метода допускают низкоуровневые операции, лучше избегать их использования, если они не вписываются в сферу работы.

Как всегда, доступен полный исходный код этой статьиover on Github.