Класс MethodHandle
- Все реализованные интерфейсы:
Constable
public abstract sealed class MethodHandle extends Object implements Constable
Содержимое дескриптора метода
Дескрипторы методов динамически и строго типизированы в соответствии с типами их параметров и возвращаемых значений. Они не различаются по имени или определяющему классу базовых методов. Дескриптор метода необходимо вызывать с помощью символьного дескриптора типа, соответствующего его собственному дескриптору типа. Каждый дескриптор метода сообщает свой дескриптор типа с помощью метода доступа type. Этот дескриптор типа представляет собой объект MethodType, структура которого состоит из последовательности классов, один из которых является типом возвращаемого значения метода (или void.class, если его нет).
Тип дескриптора метода определяет типы принимаемых им вызовов и виды применимых к нему преобразований.
Дескриптор метода содержит пару специальных вызываемых методов, называемых invokeExact и invoke. Оба вызываемых метода обеспечивают непосредственный доступ к базовому методу, конструктору, полю или другой операции дескриптора метода с учетом преобразований аргументов и возвращаемых значений. Оба вызываемых метода принимают вызовы, точно соответствующие собственному типу дескриптора метода. Обычный вызываемый метод с неточным типом также принимает ряд вызовов с другими типами.
Дескрипторы методов неизменяемы и не имеют видимого состояния. Разумеется, они могут быть связаны с базовыми методами или данными, имеющими состояние. В соответствии с моделью памяти Java любой дескриптор метода ведет себя так, как если бы все его (внутренние) поля были финальными переменными. Это означает, что любой дескриптор метода, ставший видимым приложению, всегда будет полностью сформирован. Это верно даже в том случае, если дескриптор метода опубликован через общую переменную при гонке данных.
Пользователь не может создавать подклассы дескрипторов методов. Реализации могут (но не обязаны) создавать внутренние подклассы 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, либо перехватывать локально все объекты 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, иначе — в дескриптор с фиксированным числом аргументов. |
Методы, объявленные в классе Object
clone, equals, finalize, getClass, hashCode, notify, notifyAll, wait, wait, wait | Модификатор и тип | Метод | Описание |
|---|---|---|
protected Object |
clone() |
Создает и возвращает копию этого объекта. |
boolean |
equals |
Указывает, равен ли этот объект какому-либо другому объекту. |
protected void |
finalize() |
Устарело, подлежит удалению: этот элемент API может быть удален в будущей версии. Финализация объявлена устаревшей и подлежит удалению в одном из будущих выпусков. |
final Class |
getClass() |
Возвращает класс времени выполнения этого Object. |
int |
hashCode() |
Возвращает хеш-код этого объекта. |
final void |
notify() |
Пробуждает один поток, ожидающий на мониторе этого объекта. |
final void |
notifyAll() |
Пробуждает все потоки, ожидающие на мониторе этого объекта. |
final void |
wait() |
Заставляет текущий поток ожидать пробуждения, обычно в результате уведомления или прерывания. |
final void |
wait |
Заставляет текущий поток ожидать пробуждения, обычно в результате уведомления или прерывания, либо истечения заданного времени. |
final void |
wait |
Заставляет текущий поток ожидать пробуждения, обычно в результате уведомления или прерывания, либо истечения заданного времени. |
Подробное описание методов
type
public MethodType type()
invokeExact должен точно соответствовать этому типу.- Возвращает:
- тип дескриптора метода
invokeExact
public final Object invokeExact(Object... args) throws Throwable
invokeExact должен точно соответствовать type этого дескриптора метода. Преобразования аргументов и возвращаемых значений не допускаются. При просмотре этого метода через API базовой рефлексии он будет выглядеть как один нативный метод, принимающий массив объектов и возвращающий объект. При прямом вызове этого нативного метода через 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 базовой рефлексии он будет выглядеть как один нативный метод, принимающий массив объектов и возвращающий объект. При прямом вызове этого нативного метода через 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 базовой рефлексии и 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.
При вызове адаптер заменяет завершающий аргумент-массив его элементами, каждый из которых передаётся целевому объекту как отдельный аргумент. (Порядок аргументов сохраняется.) Они попарно преобразуются посредством приведения и/или распаковки к типам завершающих параметров целевого объекта. Затем вызывается целевой объект. Возвращаемое им значение адаптер передаёт без изменений.
Перед вызовом целевого объекта адаптер проверяет, содержит ли массив ровно столько элементов, сколько необходимо для правильного числа аргументов целевого дескриптора метода. (Если требуется ноль элементов, массив также может быть null.)
При вызове адаптера длина переданного аргумента array запрашивается так, как если бы использовался байткод array.length или arraylength. Если адаптер принимает завершающий аргумент-массив нулевой длины, переданный аргумент array может быть либо массивом нулевой длины, либо null; в противном случае адаптер выбросит NullPointerException, если массив равен null, и выбросит 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имеет недопустимое значение (отрицательное или такое, что вместе с arrayLength превышает число аргументов) либо результирующий тип дескриптора метода содержит слишком много параметров -
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.