Класс MethodHandle
- Все реализуемые интерфейсы:
Constable
public abstract sealed class MethodHandle extends Object implements Constable
Содержимое дескриптора метода
Дескрипторы методов динамически и строго типизированы в соответствии с типами параметров и возвращаемого значения. Они не различаются по имени или классу, в котором определены базовые методы. Дескриптор метода необходимо вызывать с использованием символьного дескриптора типа, совпадающего с его собственным дескриптором типа. Каждый дескриптор метода предоставляет свой дескриптор типа с помощью метода доступа type. Этот дескриптор типа представляет собой объект MethodType, структура которого состоит из последовательности классов, один из которых является типом возвращаемого значения метода (или void.class, если возвращаемого значения нет).
Тип дескриптора метода определяет типы принимаемых им вызовов и виды преобразований, которые к нему применимы.
Дескриптор метода содержит пару специальных методов-вызовов, называемых invokeExact и invoke. Оба метода-вызова обеспечивают прямой доступ к базовому методу, конструктору, полю или другой операции дескриптора метода с учетом преобразований аргументов и возвращаемых значений. Оба метода принимают вызовы, в точности соответствующие типу самого дескриптора метода. Обычный, неточный метод-вызов также принимает ряд вызовов с другими типами.
Дескрипторы методов неизменяемы и не имеют видимого состояния. Разумеется, они могут быть связаны с базовыми методами или данными, имеющими состояние. С точки зрения модели памяти Java любой дескриптор метода ведет себя так, как если бы все его (внутренние) поля были final-переменными. Это означает, что любой дескриптор метода, доступный приложению, всегда будет полностью сформирован. Это верно, даже если дескриптор метода опубликован через общую переменную в условиях гонки данных.
Пользователь не может создавать подклассы дескрипторов методов. Реализации могут создавать внутренние подклассы MethodHandle, которые могут быть видны при вызове метода Object.getClass. Программисту не следует делать выводы о дескрипторе метода на основании его конкретного класса, поскольку иерархия классов дескрипторов методов (если она существует) может время от времени меняться или различаться в реализациях разных поставщиков.
Компиляция вызовов дескрипторов методов
Выражение вызова метода Java, в котором указываетсяinvokeExact или invoke, позволяет вызывать дескриптор метода из исходного кода Java. С точки зрения исходного кода эти методы могут принимать любые аргументы, а их результат можно привести к любому типу возвращаемого значения. Формально это достигается за счет того, что методы-вызовы объявлены с типами возвращаемого значения Object и аргументами переменной арности Object, но у них есть дополнительное свойство, называемое полиморфизмом сигнатуры, которое напрямую связывает эту свободу вызова со стеком выполнения JVM. Как и обычно для виртуальных методов, вызовы invokeExact и invoke на уровне исходного кода компилируются в инструкцию invokevirtual. В отличие от обычного поведения, компилятор должен записать фактические типы аргументов и не должен выполнять преобразования аргументов при вызове метода. Вместо этого он должен сгенерировать инструкции, помещающие их в стек с исходными типами, без преобразования. Перед аргументами в стек помещается сам объект дескриптора метода. Затем компилятор генерирует инструкцию invokevirtual, вызывающую дескриптор метода с символьным дескриптором типа, описывающим типы аргументов и возвращаемого значения.
Чтобы сформировать полный символьный дескриптор типа, компилятор должен также определить тип возвращаемого значения. Он определяется приведением типа в выражении вызова метода, если оно есть; в противном случае используется Object, если вызов является выражением, или void, если вызов является оператором. Приведение может выполняться к примитивному типу (но не к void).
В частном случае аргумент null без приведения типа получает символьный дескриптор типа java.lang.Void. Неоднозначность с типом Void не имеет значения, поскольку ссылок типа Void не существует, за исключением нулевой ссылки.
Вызов дескриптора метода
При первом выполнении инструкцииinvokevirtual она связывается путем символьного разрешения имен в инструкции и проверки статической допустимости вызова метода. Это также относится к вызовам invokeExact и invoke. В этом случае проверяется синтаксическая корректность символьного дескриптора типа, сформированного компилятором, и разрешаются содержащиеся в нем имена. Таким образом, инструкция invokevirtual, вызывающая дескриптор метода, всегда будет успешно связана, если символьный дескриптор типа имеет корректный синтаксис и соответствующие типы существуют. После связывания, при выполнении инструкции invokevirtual JVM сначала проверяет тип полученного дескриптора метода, чтобы убедиться, что он соответствует символьному дескриптору типа. Если типы не совпадают, значит, метод, вызываемый вызывающим кодом, отсутствует в конкретном вызываемом дескрипторе метода.
В случае invokeExact дескриптор типа вызова (после разрешения символьных имен типов) должен в точности совпадать с типом полученного дескриптора метода. В случае обычного, неточного invoke разрешенный дескриптор типа должен быть допустимым аргументом метода asType получателя. Таким образом, обычный invoke допускает больше вариантов, чем invokeExact.
После сопоставления типов вызов invokeExact напрямую и немедленно вызывает базовый метод дескриптора метода (или выполняет другое действие, в зависимости от ситуации).
Вызов обычного invoke работает так же, как вызов invokeExact, если символьный дескриптор типа, заданный вызывающим кодом, в точности совпадает с собственным типом дескриптора метода. При несовпадении типов invoke пытается скорректировать тип полученного дескриптора метода, как при вызове asType, чтобы получить дескриптор метода M2, который можно вызвать с точным совпадением типов. Это обеспечивает более гибкое согласование типов методов между вызывающим и вызываемым кодом.
(Примечание. Скорректированный дескриптор метода M2 непосредственно недоступен для наблюдения, поэтому реализациям не требуется создавать его.)
Проверка вызовов
В типичных программах сопоставление типов дескрипторов методов обычно завершается успешно. Если же сопоставление не удается, JVM выбрасывает исключениеWrongMethodTypeException напрямую (в случае invokeExact) или косвенно, как при неудачном вызове asType (в случае invoke). Таким образом, несоответствие типов методов, которое в программе со статической типизацией могло бы проявиться как ошибка связывания, в программе с дескрипторами методов может проявиться как динамическое WrongMethodTypeException.
Поскольку типы методов содержат «живые» объекты Class, при сопоставлении типов методов учитываются как имена типов, так и загрузчики классов. Поэтому, даже если дескриптор метода M создан в одном загрузчике классов L1 и используется в другом L2, вызовы дескрипторов методов остаются типобезопасными: символьный дескриптор типа вызывающего кода, разрешенный в L2, сопоставляется с символьным дескриптором типа исходного метода вызываемого кода, разрешенным в L1. Разрешение в L1 происходит при создании M и присвоении ему типа, а разрешение в L2 — при связывании инструкции invokevirtual.
За исключением проверок дескриптора типа, возможности дескриптора метода по вызову базового метода ничем не ограничены. Если класс, имеющий доступ к непубличному методу, создает дескриптор метода для этого метода, полученный дескриптор может использоваться в любом месте любым вызывающим кодом, которому передана ссылка на него.
В отличие от Core Reflection API, где доступ проверяется при каждом вызове рефлексивного метода, проверка доступа для дескриптора метода выполняется при его создании. В случае ldc (см. ниже) проверка доступа выполняется при связывании записи пула констант, лежащей в основе константы дескриптора метода.
Поэтому дескрипторы непубличных методов или методов непубличных классов, как правило, следует хранить в секрете. Их не следует передавать недоверенному коду, если только их использование этим кодом не является безопасным.
Создание дескрипторов методов
Код Java может создать дескриптор метода, напрямую обращающийся к любому методу, конструктору или полю, доступному этому коду. Для этого используется рефлексивный API на основе возможностей, называемыйMethodHandles.Lookup. Например, статический дескриптор метода можно получить с помощью Lookup.findStatic. Также существуют методы преобразования объектов Core Reflection API, например Lookup.unreflect. Подобно классам и строкам, дескрипторы методов, соответствующие доступным полям, методам и конструкторам, также можно непосредственно представить в пуле констант файла класса в виде констант, загружаемых байт-кодами ldc. Запись пула констант нового типа, CONSTANT_MethodHandle, напрямую ссылается на связанную запись пула констант CONSTANT_Methodref, CONSTANT_InterfaceMethodref или CONSTANT_Fieldref. (Полную информацию о константах дескрипторов методов см. в разделах 4.4.8 и 5.4.3.5 спецификации виртуальной машины Java.)
Дескрипторы методов, полученные с помощью поиска или загрузки констант, соответствующих методам или конструкторам с битом модификатора переменной арности (0x0080), также имеют переменную арность, как если бы они были определены с помощью asVarargsCollector или withVarargs.
Ссылка на метод может указывать как на статический, так и на нестатический метод. В случае нестатического метода тип дескриптора метода включает явный аргумент-получатель, добавленный перед всеми остальными аргументами. В типе дескриптора метода начальный аргумент-получатель типизирован в соответствии с классом, в контексте которого изначально был запрошен метод. (Например, если нестатический дескриптор метода получен через ldc, типом получателя будет класс, указанный в записи пула констант.)
Константы дескрипторов методов подчиняются тем же проверкам доступа во время связывания, что и соответствующие им инструкции байт-кода; инструкция ldc выбрасывает соответствующие ошибки связывания, если такие ошибки возникли бы при выполнении байт-кода.
Следствием этого является то, что доступ к защищенным членам ограничен получателями типа обращающегося класса или одного из его подклассов, а сам обращающийся класс должен быть подклассом (или классом из того же пакета), что и класс, в котором определен защищенный член. Если ссылка на метод указывает на защищенный нестатический метод или поле класса за пределами текущего пакета, тип аргумента-получателя сужается до типа обращающегося класса.
При вызове дескриптора виртуального метода поиск метода всегда выполняется в получателе (то есть первом аргументе).
Можно также создать невиртуальный дескриптор конкретной реализации виртуального метода. При таком вызове виртуальный поиск на основе типа получателя не выполняется. Этот дескриптор метода имитирует действие инструкции invokespecial для того же метода. Невиртуальный дескриптор метода также можно создать для имитации действия инструкции invokevirtual или invokeinterface для закрытого метода (в соответствующих случаях).
Примеры использования
Ниже приведены примеры использования:Object x, y; String s; int i;
MethodType mt; MethodHandle mh;
MethodHandles.Lookup lookup = MethodHandles.lookup();
// mt is (char,char)String
mt = MethodType.methodType(String.class, char.class, char.class);
mh = lookup.findVirtual(String.class, "replace", mt);
s = (String) mh.invokeExact("daddy",'d','n');
// invokeExact(Ljava/lang/String;CC)Ljava/lang/String;
assertEquals(s, "nanny");
// weakly typed invocation (using MHs.invoke)
s = (String) mh.invokeWithArguments("sappy", 'p', 'v');
assertEquals(s, "savvy");
// mt is (Object[])List
mt = MethodType.methodType(java.util.List.class, Object[].class);
mh = lookup.findStatic(java.util.Arrays.class, "asList", mt);
assert(mh.isVarargsCollector());
x = mh.invoke("one", "two");
// invoke(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/Object;
assertEquals(x, java.util.Arrays.asList("one","two"));
// mt is (Object,Object,Object)Object
mt = MethodType.genericMethodType(3);
mh = mh.asType(mt);
x = mh.invokeExact((Object)1, (Object)2, (Object)3);
// invokeExact(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
assertEquals(x, java.util.Arrays.asList(1,2,3));
// mt is ()int
mt = MethodType.methodType(int.class);
mh = lookup.findVirtual(java.util.List.class, "size", mt);
i = (int) mh.invokeExact(java.util.Arrays.asList(1,2,3));
// invokeExact(Ljava/util/List;)I
assert(i == 3);
mt = MethodType.methodType(void.class, String.class);
mh = lookup.findVirtual(java.io.PrintStream.class, "println", mt);
mh.invokeExact(System.out, "Hello, world.");
// invokeExact(Ljava/io/PrintStream;Ljava/lang/String;)V
invokeExact или обычного invoke создает одну инструкцию invokevirtual с символьным дескриптором типа, указанным в следующем комментарии. В этих примерах предполагается, что вспомогательный метод assertEquals вызывает Objects.equals для своих аргументов и утверждает, что результат равен true. Исключения
Для методовinvokeExact и invoke объявлено, что они выбрасывают Throwable, то есть статических ограничений на исключения, которые может выбросить дескриптор метода, нет. Поскольку JVM не различает проверяемые и непроверяемые исключения (кроме различий между их классами), указание проверяемых исключений для вызовов дескрипторов методов не влияет на форму байт-кода. Однако в исходном коде Java методы, выполняющие вызовы дескрипторов методов, должны либо явно объявлять возможность выброса Throwable, либо перехватывать все исключения локально, повторно выбрасывая только те, которые допустимы в данном контексте, и оборачивая недопустимые. Полиморфизм сигнатуры
Необычное поведение компиляции и связывания методовinvokeExact и обычного invoke обозначается термином полиморфизм сигнатуры. Согласно определению в спецификации языка Java, полиморфный по сигнатуре метод может работать с широким диапазоном сигнатур вызова и типов возвращаемых значений. В исходном коде вызов полиморфного по сигнатуре метода компилируется независимо от запрошенного символьного дескриптора типа. Как обычно, компилятор Java генерирует инструкцию invokevirtual для указанного метода с заданным символьным дескриптором типа. Необычно здесь то, что символьный дескриптор типа формируется на основе фактических типов аргументов и возвращаемого значения, а не объявления метода.
Когда JVM обрабатывает байт-код с полиморфными по сигнатуре вызовами, она успешно связывает любой такой вызов независимо от его символьного дескриптора типа. (Для обеспечения типобезопасности JVM защищает такие вызовы подходящими динамическими проверками типов, описанными в других разделах.)
Генераторы байт-кода, включая внутреннюю часть компилятора, обязаны выдавать для этих методов непреобразованные символьные дескрипторы типов. Инструменты, определяющие символьные связи, обязаны принимать такие непреобразованные дескрипторы, не сообщая об ошибках связывания.
Взаимодействие дескрипторов методов и Core Reflection API
С помощью фабричных методов APILookup любой член класса, представленный объектом Core Reflection API, можно преобразовать в поведенчески эквивалентный дескриптор метода. Например, рефлексивный объект Method можно преобразовать в дескриптор метода с помощью Lookup.unreflect. Полученные дескрипторы методов обычно обеспечивают более прямой и эффективный доступ к членам базового класса. В особом случае, когда Core Reflection API используется для представления полиморфных по сигнатуре методов invokeExact или обычного invoke из этого класса, они выглядят как обычные неполиморфные методы. Их рефлексивное представление, доступное через Class.getDeclaredMethod, не зависит от их специального статуса в этом API. Например, Method.getModifiers вернет в точности те биты модификаторов, которые требовались бы для аналогично объявленного метода, включая в данном случае биты native и varargs.
Как и любой рефлексивный метод, эти методы (в рефлексивном представлении) можно вызывать через java.lang.reflect.Method.invoke. Однако такие рефлексивные вызовы не приводят к вызовам дескрипторов методов. Такой вызов, если ему передать требуемый аргумент (один аргумент типа Object[]), проигнорирует аргумент и выбросит UnsupportedOperationException.
Поскольку инструкции invokevirtual могут напрямую вызывать дескрипторы методов с любым символьным дескриптором типа, такое рефлексивное представление противоречит обычному способу представления этих методов в байт-коде. Поэтому эти два нативных метода при рефлексивном представлении через Class.getDeclaredMethod можно считать лишь заполнителями.
Чтобы получить метод-вызов для конкретного дескриптора типа, используйте MethodHandles.exactInvoker или MethodHandles.invoker. API Lookup.findVirtual также позволяет получить дескриптор метода для вызова invokeExact или обычного invoke с любым указанным дескриптором типа.
Взаимодействие дескрипторов методов и обобщений Java
Дескриптор метода можно получить для метода, конструктора или поля, объявленного с использованием обобщенных типов Java. Как и в Core Reflection API, тип дескриптора метода строится на основе стирания типа исходного кода. При вызове дескриптора метода типы его аргументов или тип приведения возвращаемого значения могут быть обобщенными типами или экземплярами типов. В таком случае компилятор заменяет их стертой формой при формировании символьного дескриптора типа для инструкцииinvokevirtual. Дескрипторы методов не представляют типы своих функций с помощью параметризованных (обобщенных) типов Java, поскольку между типами функций и параметризованными типами Java есть три несоответствия.
- Типы методов охватывают все возможные значения арности: от отсутствия аргументов до максимально допустимого числа аргументов. Обобщенные типы не поддерживают переменное число аргументов и поэтому не могут представить это.
- Типы методов могут задавать аргументы примитивных типов, а обобщенные типы Java не могут включать примитивные типы.
- Функции высшего порядка над дескрипторами методов (комбинаторы) часто обобщены для широкого диапазона типов функций, в том числе с разной арностью. Такое обобщение невозможно представить с помощью параметра типа Java.
Ограничения на арность
JVM устанавливает абсолютное ограничение в 255 аргументов в стеке для всех методов и конструкторов любого вида. В некоторых случаях это ограничение может быть еще более строгим:- Аргумент типа
longилиdoubleсчитается двумя слотами аргументов при подсчете арности. - Нестатический метод использует дополнительный аргумент для объекта, на котором вызывается метод.
- Конструктор использует дополнительный аргумент для создаваемого объекта.
- Поскольку метод
invokeдескриптора метода (или другой полиморфный по сигнатуре метод) является невиртуальным, для самого дескриптора метода требуется дополнительный аргумент в дополнение к любому невиртуальному объекту-получателю.
IllegalArgumentException. В частности, арность типа дескриптора метода не должна быть равна максимальному значению 255.- Начиная с версии:
- 1.7
- См. также:
Краткое описание методов
| Модификатор и тип | Метод | Описание |
|---|---|---|
MethodHandle |
asCollector |
Создает дескриптор метода, собирающий аргументы в массив: он принимает заданное число позиционных аргументов, начиная с указанной позиции, и собирает их в аргумент-массив. |
MethodHandle |
asCollector |
Создает дескриптор метода, собирающий аргументы в массив: он принимает заданное число конечных позиционных аргументов и собирает их в аргумент-массив. |
MethodHandle |
asFixedArity() |
Создает дескриптор метода с фиксированной арностью, в остальном эквивалентный текущему дескриптору метода. |
MethodHandle |
asSpreader |
Создает дескриптор метода, распределяющий элементы массива: он принимает аргумент-массив в указанной позиции и распределяет его элементы как позиционные аргументы вместо массива. |
MethodHandle |
asSpreader |
Создает дескриптор метода, распределяющий элементы массива: он принимает конечный аргумент-массив и распределяет его элементы как позиционные аргументы. |
final MethodHandle |
asType |
Создает адаптер дескриптора метода, который преобразует тип текущего дескриптора метода в новый тип. |
MethodHandle |
asVarargsCollector |
Создает адаптер с переменной арностью, принимающий любое число конечных позиционных аргументов и собирающий их в аргумент-массив. |
MethodHandle |
bindTo |
Связывает значение x с первым аргументом дескриптора метода, не вызывая его. |
Optional |
describeConstable() |
Возвращает номинальный дескриптор для этого экземпляра, если его можно создать, или пустой объект Optional, если создать его нельзя. |
final Object |
invoke |
Вызывает дескриптор метода, допуская любой дескриптор типа вызывающего кода и, при необходимости, выполняя преобразования аргументов и возвращаемых значений. |
final Object |
invokeExact |
Вызывает дескриптор метода, допуская любой дескриптор типа вызывающего кода, но требуя точного совпадения типов. |
Object |
invokeWithArguments |
Выполняет вызов с переменной арностью, передавая дескриптору метода аргументы из заданного массива, как если бы это был неточный вызов invoke из точки вызова, в которой указан только тип Object, а фактическое число аргументов равно длине массива аргументов. |
Object |
invokeWithArguments |
Выполняет вызов с переменной арностью, передавая дескриптору метода аргументы из заданного списка, как если бы это был неточный вызов invoke из точки вызова, в которой указан только тип Object, а фактическое число аргументов равно длине списка аргументов. |
boolean |
isVarargsCollector() |
Определяет, поддерживает ли этот дескриптор метода вызовы с переменной арностью. |
String |
toString() |
Возвращает строковое представление дескриптора метода, начинающееся со строки "MethodHandle" и заканчивающееся строковым представлением типа дескриптора метода. |
MethodType |
type() |
Возвращает тип этого дескриптора метода. |
MethodHandle |
withVarargs |
Преобразует этот дескриптор метода в дескриптор с переменной арностью, если логический флаг равен true, или с фиксированной арностью в противном случае. |
Подробное описание методов
type
public MethodType type()
invokeExact должен в точности соответствовать этому типу.- Возвращает:
- тип дескриптора метода
invokeExact
public final Object invokeExact(Object... args) throws Throwable
invokeExact должен в точности совпадать с type этого дескриптора метода. Преобразования аргументов или возвращаемых значений не допускаются. При просмотре этого метода через API Core Reflection он будет выглядеть как один нативный метод, принимающий массив объектов и возвращающий объект. При прямом вызове этого нативного метода через java.lang.reflect.Method.invoke, через JNI или косвенно через Lookup.unreflect он выбросит UnsupportedOperationException.
- Параметры:
-
args— список параметров с полиморфной сигнатурой, статически представленный с помощью varargs - Возвращает:
- результат с полиморфной сигнатурой, статически представленный с помощью
Object - Выбрасывает:
-
WrongMethodTypeException— если тип целевого метода не идентичен символьному дескриптору типа вызывающей стороны -
Throwable— любое исключение, выброшенное нижележащим методом, без изменений передаётся через вызов дескриптора метода
invoke
public final Object invoke(Object... args) throws Throwable
Если символьный дескриптор типа в точке вызова в точности совпадает с type этого дескриптора метода, вызов выполняется так же, как при использовании invokeExact.
В противном случае вызов выполняется так, как если бы этот дескриптор метода сначала был скорректирован вызовом asType, чтобы привести его к требуемому типу, а затем вызов выполнялся так же, как при использовании invokeExact для скорректированного дескриптора метода.
Нет гарантии, что вызов asType действительно будет выполнен. Если JVM может предсказать результаты вызова, она может напрямую применить преобразования к аргументам вызывающей стороны и вызвать целевой дескриптор метода в соответствии с его собственным точным типом.
Разрешённый дескриптор типа в точке вызова invoke должен быть допустимым аргументом для метода получателя asType. В частности, если вызываемый метод не является сборщиком переменного числа аргументов, вызывающая сторона должна указать такое же количество аргументов, как и тип вызываемого метода.
При просмотре этого метода через API Core Reflection он будет выглядеть как один нативный метод, принимающий массив объектов и возвращающий объект. При прямом вызове этого нативного метода через java.lang.reflect.Method.invoke, через JNI или косвенно через Lookup.unreflect он выбросит UnsupportedOperationException.
- Параметры:
-
args— список параметров с полиморфной сигнатурой, статически представленный с помощью varargs - Возвращает:
- результат с полиморфной сигнатурой, статически представленный с помощью
Object - Выбрасывает:
-
WrongMethodTypeException— если тип целевого метода нельзя привести к символьному дескриптору типа вызывающей стороны -
ClassCastException— если тип целевого метода можно привести к типу вызывающей стороны, но проверка приведения ссылки завершается неудачно -
Throwable— любое исключение, выброшенное нижележащим методом, без изменений передаётся через вызов дескриптора метода
invokeWithArguments
public Object invokeWithArguments(Object... arguments) throws Throwable
invoke из точки вызова, в которой указан только тип Object, а фактическое количество аргументов равно длине массива аргументов. В частности, выполнение происходит в соответствии со следующими шагами, хотя нет гарантии, что методы будут вызваны, если JVM может предсказать их эффекты.
- Определить длину массива аргументов как
N. Для нулевой ссылки —N=0. - Собрать
Nэлементы массива в логический список аргументов, статический тип каждого аргумента —Object. - Определить количество параметров типа этого дескриптора метода как
M. - Определить общий тип
TNдляNаргументов илиMаргументов, если это число меньшеN, какTN=MethodType.genericMethodType(Math.min(N, M)). - Если
NбольшеM, выполнить следующие проверки и действия, чтобы сократить логический список аргументов:- Проверить, что этот дескриптор метода принимает переменное число аргументов и имеет последний параметр некоторого типа массива
A[]. В противном случае завершиться с ошибкойWrongMethodTypeException. - Собрать конечные элементы (их
N-M+1) из логического списка аргументов в один массив типаA[], применяя преобразованияasTypeдля преобразования каждого конечного аргумента к типуA. - Если какое-либо из этих преобразований невозможно, завершиться с ошибкой
ClassCastException, если какой-либо конечный элемент нельзя привести кA, или с ошибкойNullPointerException, если какой-либо конечный элемент — этоnull, аAне является ссылочным типом. - Заменить логические аргументы, собранные в массив типа
A[], самим массивом, сократив тем самым список аргументов до длиныM. Этот последний аргумент сохраняет статический типA[]. - Скорректировать тип
TN, изменив тип параметра номерNсObjectнаA[].
- Проверить, что этот дескриптор метода принимает переменное число аргументов и имеет последний параметр некоторого типа массива
- Привести исходный целевой дескриптор метода
MH0к требуемому типу, какMH1 = MH0.asType(TN). - Развернуть список аргументов в
Nотдельных аргументовA0, .... - Вызвать дескриптор метода с приведённым типом, передав ему развёрнутые аргументы: MH1.invokeExact(A0, ...).
- Получить возвращаемое значение как ссылку
Object.
Если целевой дескриптор метода принимает переменное число аргументов и список аргументов длиннее этого числа, избыточные аргументы, начиная с позиции последнего аргумента-массива, будут собраны (если возможно, с помощью преобразований asType) в массив соответствующего типа, после чего вызов будет выполнен с сокращённым списком аргументов. Таким образом можно единообразно обрабатывать очень длинные списки аргументов, которые иначе заняли бы более 254 слотов.
В отличие от режима вызова generic, который может повторно использовать аргумент-массив, передавая его целевому методу напрямую, этот режим вызова всегда создаёт новый параметр-массив, даже если исходный массив, переданный в invokeWithArguments, можно было бы передать целевому методу как прямой аргумент. Даже если количество M фактических аргументов равно арности N, а последний аргумент динамически является подходящим массивом типа A[], он всё равно будет упакован в новый массив из одного элемента, поскольку точка вызова статически задаёт тип аргумента как Object, а не как тип массива. Это не особое правило для данного метода, а обычное следствие прав вызова с переменным числом аргументов.
Вследствие шага asType при необходимости применяются следующие преобразования аргументов:
- приведение ссылок
- распаковка
- примитивные расширяющие преобразования
- преобразование для переменного числа аргументов
Возвращаемый вызовом результат упаковывается, если он имеет примитивный тип, или принудительно заменяется на null, если тип возвращаемого значения — void.
В отличие от методов с полиморфной сигнатурой invokeExact и invoke, к invokeWithArguments можно обращаться обычным образом через API Core Reflection и JNI. Поэтому его можно использовать как мост между нативным или рефлексивным кодом и дескрипторами методов.
- Примечание API:
- Этот вызов приблизительно эквивалентен следующему коду:
// for jumbo argument lists, adapt varargs explicitly: int N = (arguments == null? 0: arguments.length); int M = this.type.parameterCount(); int MAX_SAFE = 127; // 127 longs require 254 slots, which is OK if (N > MAX_SAFE && N > M && this.isVarargsCollector()) { Class<?> arrayType = this.type().lastParameterType(); Class<?> elemType = arrayType.getComponentType(); if (elemType != null) { Object args2 = Array.newInstance(elemType, M); MethodHandle arraySetter = MethodHandles.arrayElementSetter(arrayType); for (int i = 0; i < M; i++) { arraySetter.invoke(args2, i, arguments[M-1 + i]); } arguments = Arrays.copyOf(arguments, M); arguments[M-1] = args2; return this.asFixedArity().invokeWithArguments(arguments); } } // done with explicit varargs processing // Handle fixed arity and non-jumbo variable arity invocation. MethodHandle invoker = MethodHandles.spreadInvoker(this.type(), 0); Object result = invoker.invokeExact(this, arguments); - Параметры:
-
arguments— аргументы, передаваемые целевому методу - Возвращает:
- результат, возвращённый целевым методом
- Выбрасывает:
-
ClassCastException— если аргумент нельзя преобразовать посредством приведения ссылки -
WrongMethodTypeException— если тип целевого метода нельзя привести к типу, принимающему указанное количество аргументовObject -
Throwable— любое исключение, выброшенное при вызове целевого метода - См. также:
invokeWithArguments
public Object invokeWithArguments(List<?> arguments) throws Throwable
invoke из точки вызова, в которой указан только тип Object, а фактическое количество аргументов равно длине списка аргументов. Этот метод также эквивалентен следующему коду:
invokeWithArguments(arguments.toArray())
Допускаются списки очень большого размера, если этот дескриптор метода принимает переменное число аргументов. Подробности см. в описании invokeWithArguments(Object[]).
- Параметры:
-
arguments— аргументы, передаваемые целевому методу - Возвращает:
- результат, возвращённый целевым методом
- Выбрасывает:
-
NullPointerException— еслиargumentsявляется нулевой ссылкой -
ClassCastException— если аргумент нельзя преобразовать посредством приведения ссылки -
WrongMethodTypeException— если тип целевого метода нельзя привести к типу, принимающему указанное количество аргументовObject -
Throwable— любое исключение, выброшенное при вызове целевого метода
asType
public final MethodHandle asType(MethodType newType)
Если исходный и новый типы равны, возвращает this.
При вызове новый дескриптор метода выполнит следующие действия:
- Преобразует входящий список аргументов в соответствии со списком аргументов исходного дескриптора метода.
- Вызывает исходный дескриптор метода с преобразованным списком аргументов.
- Преобразует результат, возвращённый исходным дескриптором метода, в тип возвращаемого значения нового дескриптора метода.
Этот метод обеспечивает принципиальное поведенческое различие между invokeExact и обычным неточным вызовом invoke. Оба метода выполняют одинаковые действия, если дескриптор типа вызывающей стороны в точности совпадает с типом вызываемого метода. Но если типы различаются, обычный вызов invoke также вызывает asType (или некоторый внутренний эквивалент), чтобы согласовать типы вызывающей и вызываемой сторон.
Если текущий метод является дескриптором метода с переменным числом аргументов, преобразование списка аргументов может включать преобразование и сбор нескольких аргументов в массив, как описано в другом месте. Во всех остальных случаях все преобразования применяются попарно: каждый аргумент или возвращаемое значение преобразуется ровно в один аргумент или возвращаемое значение (либо в отсутствие возвращаемого значения). Применяемые преобразования определяются с учётом соответствующих компонентных типов старого и нового типов дескриптора метода.
Пусть T0 и T1 — соответствующие типы параметров нового и старого методов либо типы возвращаемых значений старого и нового методов. В частности, для некоторого допустимого индекса i пусть T0=newType.parameterType(i) и T1=this.type().parameterType(i). Или, если рассматривать обратное направление для возвращаемых значений, пусть T0=this.type().returnType() и T1=newType.returnType(). Если типы совпадают, новый дескриптор метода не изменяет соответствующий аргумент или возвращаемое значение (если оно есть). В противном случае по возможности применяется одно из следующих преобразований:
- Если T0 и T1 являются ссылочными типами, выполняется приведение к типу T1. (Типы не обязаны быть каким-либо образом связаны. Это связано с тем, что динамическое значение null можно преобразовать в любой ссылочный тип.)
- Если T0 и T1 являются примитивными типами, применяется преобразование при вызове метода Java (JLS 5.3), если оно существует. (В частности, T0 должен преобразовываться в T1 посредством примитивного расширяющего преобразования.)
- Если T0 является примитивным типом, а T1 — ссылочным, применяется преобразование приведения Java (JLS 5.5), если оно существует. (В частности, значение упаковывается из T0 в соответствующий класс-оболочку, который затем при необходимости расширяется до T1.)
- Если T0 является ссылочным типом, а T1 — примитивным, во время выполнения применяется распаковка, за которой может следовать преобразование при вызове метода Java (JLS 5.3) к примитивному значению. (Это примитивные расширяющие преобразования.) T0 должен быть классом-оболочкой или его супертипом. (Если T0 — Object, это преобразования, разрешённые методом
java.lang.reflect.Method.invoke.) Распаковка должна иметь возможность завершиться успешно: если T0 сам не является классом-оболочкой, должен существовать хотя бы один класс-оболочка TW, являющийся подтипом T0, примитивное значение из которого можно расширить до T1. - Если тип возвращаемого значения T1 помечен как void, любое возвращаемое значение отбрасывается.
- Если тип возвращаемого значения T0 — void, а T1 является ссылочным типом, вводится значение null.
- Если тип возвращаемого значения T0 — void, а T1 является примитивным типом, вводится нулевое значение.
Преобразование дескриптора метода невозможно, если невозможно выполнить хотя бы одно из требуемых попарных преобразований.
Во время выполнения преобразования ссылочных аргументов или возвращаемых значений могут требовать дополнительных проверок, которые могут завершиться неудачно. Операция распаковки может завершиться неудачно, если исходная ссылка равна null, и в этом случае возникает NullPointerException. Операция распаковки или приведение ссылки также могут завершиться неудачно, если ссылка указывает на объект неправильного типа; в этом случае возникает ClassCastException. Хотя операция распаковки может принимать несколько типов классов-оболочек, если ни один из них не подходит, будет выброшено ClassCastException.
- Параметры:
-
newType— ожидаемый тип нового дескриптора метода - Возвращает:
- дескриптор метода, делегирующий вызов
thisпосле выполнения необходимых преобразований аргументов и обеспечивающий необходимые преобразования возвращаемого значения - Выбрасывает:
-
NullPointerException— еслиnewTypeявляется нулевой ссылкой -
WrongMethodTypeException— если преобразование невозможно - См. также:
asSpreader
public MethodHandle asSpreader(Class<?> arrayType, int arrayLength)
arrayLength параметров типа целевого дескриптора заменяются одним параметром-массивом типа arrayType. Если тип элемента массива отличается от любого из соответствующих типов аргументов исходного целевого дескриптора, исходный целевой дескриптор адаптируется для непосредственного принятия элементов массива, как если бы был вызван asType.
При вызове адаптер заменяет последний аргумент-массив его элементами, передавая каждый из них целевому методу как отдельный аргумент. (Порядок аргументов сохраняется.) Они преобразуются попарно посредством приведения и/или распаковки к типам последних параметров целевого метода. Затем вызывается целевой метод. Адаптер без изменений возвращает результат, полученный от целевого метода.
Перед вызовом целевого метода адаптер проверяет, что массив содержит ровно столько элементов, сколько необходимо для правильного количества аргументов целевого дескриптора метода. (Массив также может быть нулевым, если требуется ноль элементов.)
При вызове адаптера длина переданного аргумента array запрашивается так, как если бы использовался байткод array.length или arraylength. Если адаптер принимает конечный аргумент-массив нулевой длины, переданный аргумент array может быть как массивом нулевой длины, так и null; в противном случае, если массив равен null, адаптер выбросит NullPointerException, а если массив содержит неправильное количество элементов — IllegalArgumentException.
Ниже приведены простые примеры дескрипторов методов с разворачиванием массива:
MethodHandle equals = publicLookup()
.findVirtual(String.class, "equals", methodType(boolean.class, Object.class));
assert( (boolean) equals.invokeExact("me", (Object)"me"));
assert(!(boolean) equals.invokeExact("me", (Object)"thee"));
// spread both arguments from a 2-array:
MethodHandle eq2 = equals.asSpreader(Object[].class, 2);
assert( (boolean) eq2.invokeExact(new Object[]{ "me", "me" }));
assert(!(boolean) eq2.invokeExact(new Object[]{ "me", "thee" }));
// try to spread from anything but a 2-array:
for (int n = 0; n <= 10; n++) {
Object[] badArityArgs = (n == 2 ? new Object[0] : new Object[n]);
try { assert((boolean) eq2.invokeExact(badArityArgs) && false); }
catch (IllegalArgumentException ex) { } // OK
}
// spread both arguments from a String array:
MethodHandle eq2s = equals.asSpreader(String[].class, 2);
assert( (boolean) eq2s.invokeExact(new String[]{ "me", "me" }));
assert(!(boolean) eq2s.invokeExact(new String[]{ "me", "thee" }));
// spread second arguments from a 1-array:
MethodHandle eq1 = equals.asSpreader(Object[].class, 1);
assert( (boolean) eq1.invokeExact("me", new Object[]{ "me" }));
assert(!(boolean) eq1.invokeExact("me", new Object[]{ "thee" }));
// spread no arguments from a 0-array or null:
MethodHandle eq0 = equals.asSpreader(Object[].class, 0);
assert( (boolean) eq0.invokeExact("me", (Object)"me", new Object[0]));
assert(!(boolean) eq0.invokeExact("me", (Object)"thee", (Object[])null));
// asSpreader and asCollector are approximate inverses:
for (int n = 0; n <= 2; n++) {
for (Class<?> a : new Class<?>[]{Object[].class, String[].class, CharSequence[].class}) {
MethodHandle equals2 = equals.asSpreader(a, n).asCollector(a, n);
assert( (boolean) equals2.invokeWithArguments("me", "me"));
assert(!(boolean) equals2.invokeWithArguments("me", "thee"));
}
}
MethodHandle caToString = publicLookup()
.findStatic(Arrays.class, "toString", methodType(String.class, char[].class));
assertEquals("[A, B, C]", (String) caToString.invokeExact("ABC".toCharArray()));
MethodHandle caString3 = caToString.asCollector(char[].class, 3);
assertEquals("[A, B, C]", (String) caString3.invokeExact('A', 'B', 'C'));
MethodHandle caToString2 = caString3.asSpreader(char[].class, 2);
assertEquals("[A, B, C]", (String) caToString2.invokeExact('A', "BC".toCharArray()));
- Параметры:
-
arrayType— обычноObject[], тип аргумента-массива, из которого извлекаются разворачиваемые аргументы -
arrayLength— количество аргументов, которые нужно развернуть из входящего аргумента-массива - Возвращает:
- новый дескриптор метода, который разворачивает последний аргумент-массив перед вызовом исходного дескриптора метода
- Выбрасывает:
-
NullPointerException— еслиarrayTypeявляется нулевой ссылкой -
IllegalArgumentException— еслиarrayTypeне является типом массива, целевой метод имеет менееarrayLengthтипов параметров,arrayLengthотрицательно или тип полученного дескриптора метода содержал бы слишком много параметров -
WrongMethodTypeException— если предполагаемый вызовasTypeзавершается неудачно - См. также:
asSpreader
public MethodHandle asSpreader(int spreadArgPos, Class<?> arrayType, int arrayLength)
arrayLength параметров типа целевого дескриптора, начиная с позиции spreadArgPos (индексация с нуля), заменяются одним параметром-массивом типа arrayType. Этот метод очень похож на asSpreader(Class, int), но принимает дополнительный аргумент spreadArgPos, указывающий позицию в списке параметров, с которой следует выполнять разворачивание.
- Примечание API:
- Пример:
MethodHandle compare = LOOKUP.findStatic(Objects.class, "compare", methodType(int.class, Object.class, Object.class, Comparator.class)); MethodHandle compare2FromArray = compare.asSpreader(0, Object[].class, 2); Object[] ints = new Object[]{3, 9, 7, 7}; Comparator<Integer> cmp = (a, b) -> a - b; assertTrue((int) compare2FromArray.invoke(Arrays.copyOfRange(ints, 0, 2), cmp) < 0); assertTrue((int) compare2FromArray.invoke(Arrays.copyOfRange(ints, 1, 3), cmp) > 0); assertTrue((int) compare2FromArray.invoke(Arrays.copyOfRange(ints, 2, 4), cmp) == 0); - Параметры:
-
spreadArgPos— позиция (индекс с нуля) в списке аргументов, с которой следует начать разворачивание. -
arrayType— обычноObject[], тип аргумента-массива, из которого извлекаются разворачиваемые аргументы -
arrayLength— количество аргументов, которые нужно развернуть из входящего аргумента-массива - Возвращает:
- новый дескриптор метода, который разворачивает аргумент-массив в заданной позиции перед вызовом исходного дескриптора метода
- Выбрасывает:
-
NullPointerException— еслиarrayTypeявляется нулевой ссылкой -
IllegalArgumentException— еслиarrayTypeне является типом массива, целевой метод имеет менееarrayLengthтипов параметров,arrayLengthотрицательно,spreadArgPosимеет недопустимое значение (отрицательно либо вместе с длиной массива превышает количество аргументов) или тип полученного дескриптора метода содержал бы слишком много параметров -
WrongMethodTypeException— если предполагаемый вызовasTypeзавершается неудачно - Начиная с версии:
- 9
- См. также:
withVarargs
public MethodHandle withVarargs(boolean makeVarargs)
- Примечание API:
-
Этот метод иногда полезен при адаптации дескриптора метода, который может принимать переменное число аргументов, чтобы результирующий адаптер также принимал переменное число аргументов тогда и только тогда, когда это свойство есть у исходного дескриптора. Например, следующий код изменяет первый аргумент дескриптора
mhнаint, не изменяя его свойство переменного числа аргументов:mh.asType(mh.type().changeParameterType(0,int.class)) .withVarargs(mh.isVarargsCollector())Этот вызов приблизительно эквивалентен следующему коду:
if (makeVarargs == isVarargsCollector()) return this; else if (makeVarargs) return asVarargsCollector(type().lastParameterType()); else return asFixedArity(); - Параметры:
-
makeVarargs— true, если возвращаемый дескриптор метода должен принимать переменное число аргументов - Возвращает:
- дескриптор метода того же типа, возможно, с изменённым режимом переменного числа аргументов
- Выбрасывает:
-
IllegalArgumentException— еслиmakeVarargsравно true, а у этого дескриптора метода нет конечного параметра-массива - Начиная с версии:
- 9
- См. также:
asCollector
public MethodHandle asCollector(Class<?> arrayType, int arrayLength)
arrayType) заменяется параметрами arrayLength, тип которых является типом элементов arrayType. Если тип массива отличается от типа последнего аргумента исходного целевого дескриптора, исходный целевой дескриптор адаптируется для непосредственного принятия типа массива, как если бы был вызван asType.
При вызове адаптер заменяет свои последние аргументы arrayLength одним новым массивом типа arrayType, элементы которого содержат заменённые аргументы в исходном порядке. Затем вызывается целевой метод. Адаптер без изменений возвращает результат, полученный от целевого метода.
(Если arrayLength равно нулю, массив также может быть общей константой.)
(Примечание: arrayType часто совпадает с типом последнего параметра исходного целевого метода. Это явный аргумент для симметрии с asSpreader, а также для того, чтобы целевой метод мог использовать простой Object в качестве типа последнего параметра.)
Чтобы создать адаптер со сбором аргументов, не ограниченный определённым их количеством, используйте вместо этого asVarargsCollector или withVarargs.
Ниже приведены примеры дескрипторов методов со сбором в массив:
MethodHandle deepToString = publicLookup()
.findStatic(Arrays.class, "deepToString", methodType(String.class, Object[].class));
assertEquals("[won]", (String) deepToString.invokeExact(new Object[]{"won"}));
MethodHandle ts1 = deepToString.asCollector(Object[].class, 1);
assertEquals(methodType(String.class, Object.class), ts1.type());
//assertEquals("[won]", (String) ts1.invokeExact( new Object[]{"won"})); //FAIL
assertEquals("[[won]]", (String) ts1.invokeExact((Object) new Object[]{"won"}));
// arrayType can be a subtype of Object[]
MethodHandle ts2 = deepToString.asCollector(String[].class, 2);
assertEquals(methodType(String.class, String.class, String.class), ts2.type());
assertEquals("[two, too]", (String) ts2.invokeExact("two", "too"));
MethodHandle ts0 = deepToString.asCollector(Object[].class, 0);
assertEquals("[]", (String) ts0.invokeExact());
// collectors can be nested, Lisp-style
MethodHandle ts22 = deepToString.asCollector(Object[].class, 3).asCollector(String[].class, 2);
assertEquals("[A, B, [C, D]]", ((String) ts22.invokeExact((Object)'A', (Object)"B", "C", "D")));
// arrayType can be any primitive array type
MethodHandle bytesToString = publicLookup()
.findStatic(Arrays.class, "toString", methodType(String.class, byte[].class))
.asCollector(byte[].class, 3);
assertEquals("[1, 2, 3]", (String) bytesToString.invokeExact((byte)1, (byte)2, (byte)3));
MethodHandle longsToString = publicLookup()
.findStatic(Arrays.class, "toString", methodType(String.class, long[].class))
.asCollector(long[].class, 1);
assertEquals("[123]", (String) longsToString.invokeExact((long)123));
Примечание: полученный адаптер никогда не является дескриптором метода с переменным числом аргументов, даже если исходный целевой дескриптор метода им был.
- Параметры:
-
arrayType— частоObject[], тип аргумента-массива, в который будут собраны аргументы -
arrayLength— количество аргументов, которые нужно собрать в новый аргумент-массив - Возвращает:
- новый дескриптор метода, который собирает несколько конечных аргументов в массив перед вызовом исходного дескриптора метода
- Выбрасывает:
-
NullPointerException— еслиarrayTypeявляется нулевой ссылкой -
IllegalArgumentException— еслиarrayTypeне является типом массива,arrayTypeнельзя присвоить типу конечного параметра этого дескриптора метода,arrayLengthне является допустимым размером массива либо тип полученного дескриптора метода содержал бы слишком много параметров -
WrongMethodTypeException— если предполагаемый вызовasTypeзавершается неудачно - См. также:
asCollector
public MethodHandle asCollector(int collectArgPos, Class<?> arrayType, int arrayLength)
collectArgPos (обычно типа arrayType), заменяется на arrayLength параметров, тип которых является типом элементов arrayType. Этот метод очень похож на asCollector(Class, int), но отличается тем, что его аргумент
collectArgPos указывает позицию в списке параметров, с которой следует собирать аргументы. Индекс отсчитывается от нуля.
- Примечание к API:
- Примеры:
StringWriter swr = new StringWriter(); MethodHandle swWrite = LOOKUP.findVirtual(StringWriter.class, "write", methodType(void.class, char[].class, int.class, int.class)).bindTo(swr); MethodHandle swWrite4 = swWrite.asCollector(0, char[].class, 4); swWrite4.invoke('A', 'B', 'C', 'D', 1, 2); assertEquals("BC", swr.toString()); swWrite4.invoke('P', 'Q', 'R', 'S', 0, 4); assertEquals("BCPQRS", swr.toString()); swWrite4.invoke('W', 'X', 'Y', 'Z', 3, 1); assertEquals("BCPQRSZ", swr.toString());Примечание: Полученный адаптер никогда не является дескриптором метода с переменным числом аргументов, даже если исходный целевой дескриптор метода был таким.
- Параметры:
-
collectArgPos— позиция, отсчитываемая от нуля, в списке параметров, с которой начинается сбор аргументов. -
arrayType— частоObject[], тип аргумента-массива, в который будут собираться аргументы -
arrayLength— количество аргументов, которые нужно собрать в новый аргумент-массив - Возвращает:
- новый дескриптор метода, который собирает некоторые аргументы в массив перед вызовом исходного дескриптора метода
- Исключения:
-
NullPointerException— еслиarrayTypeявляется нулевой ссылкой -
IllegalArgumentException— еслиarrayTypeне является типом массива илиarrayTypeнельзя присвоить типу параметра-массива этого дескриптора метода, либо еслиarrayLengthне является допустимым размером массива, либо еслиcollectArgPosимеет недопустимое значение (отрицательное или превышающее количество аргументов), либо если тип полученного дескриптора метода содержал бы слишком много параметров -
WrongMethodTypeException— если предполагаемый вызовasTypeзавершается неудачно - Начиная с версии:
- 9
- См. также:
asVarargsCollector
public MethodHandle asVarargsCollector(Class<?> arrayType)
Тип и поведение адаптера будут такими же, как у цели, за исключением того, что некоторые запросы invoke и asType могут привести к сбору завершающих позиционных аргументов в завершающий параметр цели. Кроме того, типом последнего параметра адаптера будет arrayType, даже если тип последнего параметра цели отличается.
Это преобразование может вернуть this, если дескриптор метода уже принимает переменное число аргументов, а тип его завершающего параметра совпадает с arrayType.
При вызове с помощью invokeExact адаптер вызывает цель без изменения аргументов. (Примечание: это поведение отличается от поведения сборщика с фиксированным числом аргументов, поскольку он принимает целый массив неопределенной длины, а не фиксированное число аргументов.)
При обычном неточном вызове invoke, если тип вызывающего кода совпадает с типом адаптера, адаптер вызывает цель так же, как при invokeExact. (Это обычное поведение для invoke при совпадении типов.)
В противном случае, если арность вызывающего кода совпадает с арностью адаптера, а тип завершающего параметра вызывающего кода является ссылочным типом, идентичным типу завершающего параметра адаптера или присваиваемым ему, аргументы и возвращаемые значения преобразуются попарно, как при вызове asType для дескриптора метода с фиксированным числом аргументов.
В противном случае арности различаются или тип завершающего параметра адаптера не является супертипом соответствующего типа вызывающего кода. В этом случае адаптер заменяет все завершающие аргументы, начиная с исходной позиции завершающего аргумента, новым массивом типа arrayType, элементы которого состоят из замененных аргументов в исходном порядке.
Тип вызывающего кода должен предоставлять как минимум достаточное количество аргументов правильного типа, чтобы удовлетворить требование цели к позиционным аргументам, предшествующим завершающему аргументу-массиву. Таким образом, вызывающий код должен передавать как минимум N-1 аргументов, где N — арность цели. Кроме того, должны существовать преобразования входящих аргументов в аргументы цели. Как и в других случаях обычного вызова invoke, если эти основные требования не выполнены, может быть выброшено исключение WrongMethodTypeException.
Во всех случаях адаптер возвращает без изменений значение, в конечном итоге возвращенное целью.
В последнем случае поведение в точности такое, как если бы целевой дескриптор метода был временно адаптирован с помощью сборщика с фиксированным числом аргументов к арности, требуемой типом вызывающего кода. (Как и при asCollector, если длина массива равна нулю, вместо нового массива может использоваться общая константа. Если предполагаемый вызов asCollector выбросил бы исключение IllegalArgumentException или WrongMethodTypeException, вызов адаптера с переменным числом аргументов должен выбросить WrongMethodTypeException.)
Поведение asType также специализировано для адаптеров с переменным числом аргументов, чтобы сохранять инвариант: обычный неточный вызов invoke всегда эквивалентен вызову asType для изменения типа цели с последующим вызовом invokeExact. Поэтому адаптер с переменным числом аргументов отвечает на запрос asType созданием сборщика с фиксированным числом аргументов тогда и только тогда, когда адаптер и запрошенный тип различаются арностью или типом завершающего аргумента. Полученный сборщик с фиксированным числом аргументов дополнительно адаптируется к запрошенному типу (если необходимо) посредством попарного преобразования, как при еще одном применении asType.
Если дескриптор метода получен выполнением инструкции ldc для константы CONSTANT_MethodHandle, а целевой метод помечен как метод с переменным числом аргументов (с битом-модификатором 0x0080), дескриптор метода будет принимать аргументы с разной арностью, как если бы константа дескриптора метода была создана вызовом asVarargsCollector.
Чтобы создать собирающий адаптер, который собирает заранее заданное количество аргументов и тип которого отражает это количество, используйте вместо этого asCollector.
Ни одно преобразование дескриптора метода не создает новые дескрипторы методов с переменным числом аргументов, если в документации не указано обратное. Поэтому, кроме asVarargsCollector и withVarargs, все методы в MethodHandle и MethodHandles возвращают дескриптор метода с фиксированным числом аргументов, за исключением случаев, когда указано, что они возвращают исходный операнд (например, asType дескриптора метода собственного типа).
Вызов asVarargsCollector для дескриптора метода, который уже принимает переменное число аргументов, приведет к созданию дескриптора метода с теми же типом и поведением. При этом может быть возвращен исходный дескриптор метода с переменным числом аргументов, но это не гарантируется.
Ниже приведен пример дескриптора метода с переменным числом аргументов, создающего список:
MethodHandle deepToString = publicLookup()
.findStatic(Arrays.class, "deepToString", methodType(String.class, Object[].class));
MethodHandle ts1 = deepToString.asVarargsCollector(Object[].class);
assertEquals("[won]", (String) ts1.invokeExact( new Object[]{"won"}));
assertEquals("[won]", (String) ts1.invoke( new Object[]{"won"}));
assertEquals("[won]", (String) ts1.invoke( "won" ));
assertEquals("[[won]]", (String) ts1.invoke((Object) new Object[]{"won"}));
// findStatic of Arrays.asList(...) produces a variable arity method handle:
MethodHandle asList = publicLookup()
.findStatic(Arrays.class, "asList", methodType(List.class, Object[].class));
assertEquals(methodType(List.class, Object[].class), asList.type());
assert(asList.isVarargsCollector());
assertEquals("[]", asList.invoke().toString());
assertEquals("[1]", asList.invoke(1).toString());
assertEquals("[two, too]", asList.invoke("two", "too").toString());
String[] argv = { "three", "thee", "tee" };
assertEquals("[three, thee, tee]", asList.invoke(argv).toString());
assertEquals("[three, thee, tee]", asList.invoke((Object[])argv).toString());
List ls = (List) asList.invoke((Object)argv);
assertEquals(1, ls.size());
assertEquals("[three, thee, tee]", Arrays.toString((Object[])ls.get(0)));
Обсуждение: Эти правила представляют собой динамически типизированный вариант правил Java для методов с переменным числом аргументов. В обоих случаях вызывающие код метода или дескриптора метода с переменным числом аргументов могут передать ноль или более позиционных аргументов либо заранее собранные массивы произвольной длины. Пользователям следует учитывать особую роль последнего аргумента и то, как совпадение типов этого аргумента определяет, будет ли один завершающий аргумент интерпретироваться как целый массив или как отдельный элемент массива, в который он будет собран. Обратите внимание: динамический тип завершающего аргумента не влияет на это решение; учитывается только сравнение символьного дескриптора типа точки вызова с дескриптором типа дескриптора метода.
- Параметры:
-
arrayType— частоObject[], тип аргумента-массива, в который будут собираться аргументы - Возвращает:
- новый дескриптор метода, который может собирать любое количество завершающих аргументов в массив перед вызовом исходного дескриптора метода
- Исключения:
-
NullPointerException— еслиarrayTypeявляется нулевой ссылкой -
IllegalArgumentException— еслиarrayTypeне является типом массива илиarrayTypeнельзя присвоить типу завершающего параметра этого дескриптора метода - См. также:
isVarargsCollector
public boolean isVarargsCollector()
- вызовом asVarargsCollector
- вызовом метода поиска, который разрешается в метод или конструктор Java с переменным числом аргументов
- инструкцией
ldcдляCONSTANT_MethodHandle, которая разрешается в метод или конструктор Java с переменным числом аргументов
- Возвращает:
- true, если этот дескриптор метода принимает вызовы
invokeс обычным неточным вызовом более чем с одной арностью - См. также:
asFixedArity
public MethodHandle asFixedArity()
Если текущий дескриптор метода не принимает переменное число аргументов, возвращается текущий дескриптор метода. Это верно, даже если текущий дескриптор метода нельзя использовать в качестве допустимого входного значения для asVarargsCollector.
В противном случае полученный дескриптор метода с фиксированным числом аргументов будет иметь тот же тип и поведение, что и текущий дескриптор метода, за исключением того, что isVarargsCollector вернет false. Дескриптор метода с фиксированным числом аргументов может совпадать с прежним аргументом для asVarargsCollector, но это не гарантируется.
Ниже приведен пример дескриптора метода с переменным числом аргументов, создающего список:
MethodHandle asListVar = publicLookup()
.findStatic(Arrays.class, "asList", methodType(List.class, Object[].class))
.asVarargsCollector(Object[].class);
MethodHandle asListFix = asListVar.asFixedArity();
assertEquals("[1]", asListVar.invoke(1).toString());
Exception caught = null;
try { asListFix.invoke((Object)1); }
catch (Exception ex) { caught = ex; }
assert(caught instanceof ClassCastException);
assertEquals("[two, too]", asListVar.invoke("two", "too").toString());
try { asListFix.invoke("two", "too"); }
catch (Exception ex) { caught = ex; }
assert(caught instanceof WrongMethodTypeException);
Object[] argv = { "three", "thee", "tee" };
assertEquals("[three, thee, tee]", asListVar.invoke(argv).toString());
assertEquals("[three, thee, tee]", asListFix.invoke(argv).toString());
assertEquals(1, ((List) asListVar.invoke((Object)argv)).size());
assertEquals("[three, thee, tee]", asListFix.invoke((Object)argv).toString());
- Возвращает:
- новый дескриптор метода, который принимает только фиксированное количество аргументов
- См. также:
bindTo
public MethodHandle bindTo(Object x)
x к первому аргументу дескриптора метода, не вызывая его. Новый дескриптор метода адаптирует текущий дескриптор метода в качестве своей цели, привязывая его к заданному аргументу. Тип привязанного дескриптора будет таким же, как тип цели, за исключением того, что один ведущий ссылочный параметр будет исключен. При вызове привязанный дескриптор вставляет заданное значение x в качестве нового ведущего аргумента цели. Остальные аргументы также передаются без изменений. Привязанный дескриптор без изменений возвращает значение, в конечном итоге возвращенное целью.
Ссылка x должна преобразовываться к типу первого параметра цели.
Примечание: Поскольку дескрипторы методов неизменяемы, целевой дескриптор метода сохраняет исходные тип и поведение.
Примечание: Полученный адаптер никогда не является дескриптором метода с переменным числом аргументов, даже если исходный целевой дескриптор метода был таким.
- Параметры:
-
x— значение, привязываемое к первому аргументу цели - Возвращает:
- новый дескриптор метода, который добавляет заданное значение в начало списка входящих аргументов перед вызовом исходного дескриптора метода
- Исключения:
-
IllegalArgumentException— если у цели нет ведущего параметра ссылочного типа -
ClassCastException— еслиxнельзя преобразовать к ведущему типу параметра цели - См. также:
describeConstable
public Optional<MethodHandleDesc> describeConstable()
Optional, если создать его нельзя.- Определено в:
-
describeConstableв интерфейсеConstable - Возвращает:
- объект
Optional, содержащий полученный номинальный дескриптор, или пустой объектOptional, если создать его нельзя. - Начиная с версии:
- 12
toString
public String toString()
"MethodHandle" и заканчивающееся строковым представлением типа дескриптора метода. Иными словами, этот метод возвращает строку, равную значению: "MethodHandle" + type().toString()
(Примечание: В будущих версиях этого API в строковое представление может быть добавлена дополнительная информация. Поэтому приложениям не следует анализировать синтаксис текущей версии.)
© 1993, 2025, Oracle and/or its affiliates. All rights reserved.
Documentation extracted from Debian's OpenJDK Development Kit package.
Licensed under the GNU General Public License, version 2, with the Classpath Exception.
Various third party code in OpenJDK is licensed under different licenses (see Debian package).
Java and OpenJDK are trademarks or registered trademarks of Oracle and/or its affiliates.
https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/lang/invoke/MethodHandle.html