Module java.base
Package java.lang.invoke

Class MethodHandle

java.lang.Object
java.lang.invoke.MethodHandle
所有实现的接口:
Constable

public abstract sealed class MethodHandle extends Object implements Constable
方法句柄是对底层方法、构造函数、字段或类似低级操作的类型化、直接可执行引用,可选地转换参数或返回值。这些转换非常通用,包括诸如转换插入删除替换等模式。

方法句柄内容

方法句柄根据其参数和返回类型动态且强类型化。它们不是通过底层方法的名称或定义类来区分的。必须使用与方法句柄自身类型描述符匹配的符号类型描述符来调用方法句柄。

每个方法句柄通过type访问器报告其类型描述符。该类型描述符是一个MethodType对象,其结构是一系列类,其中之一是方法的返回类型(如果没有则为void.class)。

方法句柄的类型控制着它接受的调用类型以及适用于它的转换种类。

方法句柄包含一对特殊的调用者方法,称为invokeExactinvoke。这两个调用者方法提供对方法句柄的底层方法、构造函数、字段或其他操作的直接访问,经过参数和返回值的转换修改。这两个调用者接受与方法句柄自身类型完全匹配的调用。普通的、不精确的调用者还接受一系列其他调用类型。

方法句柄是不可变的,没有可见状态。当然,它们可以绑定到展示状态的底层方法或数据。就Java内存模型而言,任何方法句柄的行为都会表现得好像它的(内部)字段都是final变量。这意味着任何暴露给应用程序的方法句柄始终是完全形成的。即使方法句柄通过数据竞争中的共享变量发布,这也是正确的。

用户无法对方法句柄进行子类化。实现可能(或可能不)创建MethodHandle的内部子类,这些子类可能通过Object.getClass操作可见。程序员不应根据方法句柄的特定类来得出关于方法句柄的结论,因为方法句柄类层次结构(如果有)可能随时间或在不同供应商的不同实现之间发生变化。

方法句柄编译

Java方法调用表达式命名invokeExactinvoke可以从Java源代码调用方法句柄。从源代码的角度来看,这些方法可以接受任何参数,并且它们的结果可以转换为任何返回类型。形式上,这是通过给调用者方法Object返回类型和可变参数Object参数,但它们具有称为签名多态性的附加质量,将此调用自由直接连接到JVM执行堆栈。

与虚拟方法一样,对invokeExactinvoke的源级调用编译为invokevirtual指令。更不寻常的是,编译器必须记录实际参数类型,并且不能对参数执行方法调用转换。相反,它必须生成指令,根据它们自己未转换的类型将它们推送到堆栈上。方法句柄对象本身在参数之前被推送到堆栈上。然后,编译器生成一个invokevirtual指令,使用描述参数和返回类型的符号类型描述符调用方法句柄。

为了发出完整的符号类型描述符,编译器还必须确定返回类型。这基于方法调用表达式上的转换,如果有的话,或者如果调用是表达式,则为Object,否则为void。转换可能是到原始类型(但不是void)。

作为一个特例,未转换的null参数被赋予java.lang.Void的符号类型描述符。与类型Void的歧义是无害的,因为除了空引用之外,没有类型Void的引用。

方法句柄调用

第一次执行invokevirtual指令时,通过符号解析指令中的名称并验证调用方法是否在静态上下文中合法来进行链接。对于invokeExactinvoke的调用也是如此。在这种情况下,编译器发出的符号类型描述符将被检查其语法正确性,并且其中包含的名称将被解析。因此,调用方法句柄的invokevirtual指令将始终链接,只要符号类型描述符在语法上是良好形成的并且类型存在。

在链接后执行invokevirtual时,JVM首先检查接收方法句柄的类型,以确保它与符号类型描述符匹配。如果类型匹配失败,则表示调用方正在调用的方法不存在于被调用的单个方法句柄中。

对于invokeExact,调用的类型描述符(在解析符号类型名称后)必须与接收方法句柄的方法类型完全匹配。对于普通的、不精确的invoke,解析后的类型描述符必须是接收者的asType方法的有效参数。因此,普通的invokeinvokeExact更宽松。

类型匹配后,对invokeExact的调用直接并立即调用方法句柄的底层方法(或其他行为,视情况而定)。

对于普通的invoke的调用与对invokeExact的调用相同,如果调用者指定的符号类型描述符与方法句柄自身的类型完全匹配。如果存在类型不匹配,则invoke尝试调整接收方法句柄的类型,就像调用asType一样,以获得一个完全可调用的方法句柄M2。这允许调用方和被调用方之间进行更强大的方法类型协商。

注意:调整后的方法句柄M2不是直接可观察的,因此实现不需要实现它。)

调用检查

在典型程序中,方法句柄类型匹配通常会成功。但如果匹配失败,JVM将抛出一个WrongMethodTypeException,要么直接(在invokeExact的情况下)要么间接地,就像通过失败的asType调用一样(在invoke的情况下)。

因此,方法类型不匹配可能在静态类型化程序中显示为链接错误,在使用方法句柄的程序中可能显示为动态的WrongMethodTypeException

因为方法类型包含“活动”的Class对象,所以方法类型匹配考虑了类型名称和类加载器。因此,即使方法句柄M是在一个类加载器L1中创建的,并在另一个L2中使用,方法句柄调用也是类型安全的,因为调用方的符号类型描述符在L2中解析后,将与在L1中解析后的原始被调用方法的符号类型描述符匹配。在创建M并分配其类型时,L1中的解析发生,而在链接invokevirtual指令时,L2中的解析发生。

除了类型描述符检查外,方法句柄调用其底层方法的能力是不受限制的。如果一个方法句柄是由一个具有对该方法的访问权限的类在非公共方法上形成的,那么结果句柄可以被任何接收到它的引用的调用者在任何地方使用。

与核心反射API不同,反射方法被调用时每次都会检查访问权限,方法句柄访问权限检查是在创建方法句柄时进行的。的情况下,访问检查是作为链接常量方法句柄的常量池条目的一部分进行的。

因此,对非公共方法或非公共类中的方法的句柄通常应保密。除非从不受信任的代码中使用它们是无害的,否则不应将它们传递给不受信任的代码。

方法句柄创建

Java代码可以创建一个方法句柄,直接访问对该代码可访问的任何方法、构造函数或字段。这是通过一个反射、基于能力的API称为MethodHandles.Lookup完成的。例如,可以从Lookup.findStatic获取一个静态方法句柄。还有从核心反射API对象转换的方法,例如Lookup.unreflect

与类和字符串一样,与可访问字段、方法和构造函数对应的方法句柄也可以直接表示为类文件的常量池中的常量,以供ldc字节码加载。一种新类型的常量池条目,CONSTANT_MethodHandle,直接引用相关的CONSTANT_MethodrefCONSTANT_InterfaceMethodrefCONSTANT_Fieldref常量池条目。(有关方法句柄常量的详细信息,请参见Java虚拟机规范的第4.4.85.4.3.5节。)

通过查找或从具有可变参数修饰符位(0x0080)的方法或构造函数中产生的方法句柄或常量加载的方法句柄具有相应的可变参数性质,就好像它们是通过asVarargsCollectorwithVarargs定义的一样。

方法引用可以引用静态方法或非静态方法。在非静态情况下,方法句柄类型包括一个显式的接收器参数,位于任何其他参数之前。在方法句柄的类型中,初始接收器参数的类型根据最初请求方法的类进行标记。(例如,如果通过ldc获取非静态方法句柄,则接收器的类型是常量池条目中命名的类。)

方法句柄常量受到与其对应的字节码指令相同的链接时访问检查,如果字节码行为会引发此类错误,则ldc指令将抛出相应的链接错误。

作为这一点的推论,对受保护成员的访问仅限于访问类的接收器,或其子类之一,并且访问类必须反过来是受保护成员定义类的子类(或包同级)。如果方法引用引用当前包之外的类的受保护非静态方法或字段,则接收器参数将缩小为访问类的类型。

当调用虚方法的方法句柄时,方法总是在接收器(即第一个参数)中查找。

还可以创建对特定虚方法实现的非虚方法句柄。这些方法句柄不会根据接收器类型执行虚拟查找。这样的方法句柄模拟了对相同方法的invokespecial指令的效果。还可以创建非虚方法句柄以模拟对私有方法的invokevirtualinvokeinterface指令的效果(视情况而定)。

用法示例

以下是一些用法示例:
Object x, y; String s; int i;
MethodType mt; MethodHandle mh;
MethodHandles.Lookup lookup = MethodHandles.lookup();
// mt 是 (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");
// 弱类型调用(使用 MHs.invoke)
s = (String) mh.invokeWithArguments("sappy", 'p', 'v');
assertEquals(s, "savvy");
// mt 是 (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 是 (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 是 ()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。

异常

方法invokeExactinvoke声明为抛出Throwable,也就是说,方法句柄可以抛出任何异常。由于JVM不区分已检查异常和未检查异常(当然是根据它们的类区分),因此将已检查异常归因于方法句柄调用不会对字节码形状产生特定影响。但在Java源代码中,执行方法句柄调用的方法必须显式抛出Throwable,或者必须在本地捕获所有可抛出的异常,仅重新抛出在上下文中合法的异常,并包装不合法的异常。

签名多态

invokeExact和普通invoke的不寻常编译和链接行为被称为签名多态。根据Java语言规范的定义,签名多态方法是可以使用各种调用签名和返回类型的方法之一。

在源代码中,对签名多态方法的调用将被编译,而不管请求的符号类型描述符如何。通常情况下,Java编译器根据给定的符号类型描述符针对命名方法发出一个invokevirtual指令。不寻常的部分是,符号类型描述符是从实际参数和返回类型派生的,而不是从方法声明派生的。

当JVM处理包含签名多态调用的字节码时,它将成功链接任何这样的调用,而不管其符号类型描述符如何。(为了保持类型安全,JVM将使用适当的动态类型检查保护这些调用,如其他地方所述。)

包括编译器后端在内的字节码生成器必须为这些方法发出未转换的符号类型描述符。确定符号链接的工具必须接受这些未转换的描述符,而不报告链接错误。

方法句柄与核心反射API之间的互操作

使用Lookup API中的工厂方法,任何由核心反射API对象表示的类成员都可以转换为行为等效的方法句柄。例如,可以使用Lookup.unreflect将反射的Method转换为方法句柄。生成的方法句柄通常提供对底层类成员更直接和高效的访问。

作为一个特例,当核心反射API用于查看此类中的签名多态方法invokeExact或普通invoke时,它们会显示为普通的非多态方法。它们的反射外观,如通过Class.getDeclaredMethod查看的那样,不受其在此API中的特殊状态的影响。例如,Method.getModifiers将报告与任何类似声明的方法所需的修饰符位完全相同,包括在这种情况下的nativevarargs位。

与任何反射方法一样,这些方法(在反射时)可以通过java.lang.reflect.Method.invoke调用。但是,这样的反射调用不会导致方法句柄调用。如果传递所需的参数(一个类型为Object[]的单个参数),这样的调用将忽略该参数,并抛出UnsupportedOperationException

由于invokevirtual指令可以在任何符号类型描述符下本地调用方法句柄,因此这种反射视图与通过字节码正常呈现这些方法的方式存在冲突。因此,当通过Class.getDeclaredMethod反射查看这两个本机方法时,可以将其视为仅作为占位符。

为了获取特定类型描述符的调用程序方法,请使用MethodHandles.exactInvokerMethodHandles.invokerLookup.findVirtual API也能够返回一个方法句柄以调用invokeExact或普通invoke,对于任何指定的类型描述符。

方法句柄与Java泛型之间的互操作

可以获取声明为具有Java泛型类型的方法、构造函数或字段的方法句柄。与核心反射API一样,方法句柄的类型将从源级类型的擦除构造。当调用方法句柄时,其参数的类型或返回值的强制转换类型可能是泛型类型或类型实例。如果发生这种情况,编译器将在构造invokevirtual指令的符号类型描述符时,将这些类型替换为它们的擦除形式。

方法句柄不会根据Java参数化(泛型)类型表示其函数类型,因为函数类型和参数化Java类型之间存在三个不匹配。

  • 方法类型涵盖所有可能的参数个数,从无参数到最多允许的参数数。泛型不是可变参数的,因此无法表示这一点。
  • 方法类型可以指定原始类型的参数,而Java泛型类型无法涵盖。
  • 对方法句柄的高阶函数(组合器)通常跨多种功能类型进行泛型化,包括多个参数的功能类型。使用Java类型参数无法表示这种泛型性。

参数个数限制

JVM对任何类型的所有方法和构造函数都施加了255个堆叠参数的绝对限制。在某些情况下,这个限制可能看起来更为严格:
  • 一个longdouble参数(对于参数个数限制)计为两个参数槽。
  • 非静态方法会消耗额外的参数,用于调用方法的对象。
  • 构造函数会消耗额外的参数,用于正在构造的对象。
  • 由于方法句柄的invoke方法(或其他签名多态方法)是非虚拟的,因此它会消耗额外的参数,用于方法句柄本身,除了任何非虚拟接收器对象。
这些限制意味着仅仅因为JVM对堆叠参数的限制,某些方法句柄可能无法创建。例如,如果一个静态JVM方法接受恰好255个参数,就无法为其创建方法句柄。尝试创建具有不可能方法类型的方法句柄会导致非法参数异常。特别是,方法句柄的类型不能具有确切的最大值255。
自Java版本:
1.7
参见:
  • Method Summary

    Modifier and Type
    Method
    Description
    asCollector(int collectArgPos, Class<?> arrayType, int arrayLength)
    创建一个数组收集方法句柄,接受给定位置开始的指定数量的位置参数,并将它们收集到一个数组参数中。
    asCollector(Class<?> arrayType, int arrayLength)
    创建一个数组收集方法句柄,接受给定数量的尾随位置参数,并将它们收集到一个数组参数中。
    创建一个固定元数方法句柄,其在其他方面等效于当前方法句柄。
    asSpreader(int spreadArgPos, Class<?> arrayType, int arrayLength)
    创建一个数组展开方法句柄,接受给定位置的数组参数,并将其元素作为位置参数展开,替代数组。
    asSpreader(Class<?> arrayType, int arrayLength)
    创建一个数组展开方法句柄,接受尾随数组参数,并将其元素作为位置参数展开。
    final MethodHandle
    asType(MethodType newType)
    生成一个适配器方法句柄,将当前方法句柄的类型适配为新类型。
    asVarargsCollector(Class<?> arrayType)
    创建一个可变元数适配器,能够接受任意数量的尾随位置参数,并将它们收集到一个数组参数中。
    将值x绑定到方法句柄的第一个参数,而不调用它。
    返回此实例的名义描述符,如果可以构造一个,则返回一个空的Optional
    final Object
    invoke(Object... args)
    调用方法句柄,允许任何调用者类型描述符,并可选择对参数和返回值执行转换。
    final Object
    invokeExact(Object... args)
    调用方法句柄,允许任何调用者类型描述符,但要求精确类型匹配。
    invokeWithArguments(Object... arguments)
    执行可变元数调用,将给定数组中的参数传递给方法句柄,就像通过仅提及类型Object的调用点的不精确invoke一样,其实际参数计数为参数数组的长度。
    invokeWithArguments(List<?> arguments)
    执行可变元数调用,将给定列表中的参数传递给方法句柄,就像通过仅提及类型Object的调用点的不精确invoke一样,其实际参数计数为参数列表的长度。
    boolean
    确定此方法句柄是否支持可变元数调用。
    返回方法句柄的字符串表示形式,以字符串"MethodHandle"开头,并以方法句柄类型的字符串表示形式结尾。
    type()
    报告此方法句柄的类型。
    withVarargs(boolean makeVarargs)
    将此方法句柄调整为可变元数,如果布尔标志为true,则为固定元数

    Methods declared in class java.lang.Object

    clone, equals, finalize, getClass, hashCode, notify, notifyAll, wait, wait, wait
  • Method Details

    • type

      public MethodType type()
      报告此方法句柄的类型。每次通过invokeExact调用此方法句柄时,必须完全匹配此类型。
      返回:
      方法句柄类型
    • invokeExact

      public final Object invokeExact(Object... args) throws Throwable
      调用方法句柄,允许任何调用者类型描述符,但要求精确类型匹配。调用点的符号类型描述符必须与此方法句柄的type完全匹配。参数或返回值上不允许进行转换。

      当通过核心反射API观察此方法时,它将显示为一个单一的本机方法,接受一个对象数组并返回一个对象。如果直接通过java.lang.reflect.Method.invoke、通过JNI或间接通过Lookup.unreflect调用此本机方法,它将抛出UnsupportedOperationException

      参数:
      args - 使用可变参数静态表示的签名多态参数列表
      返回:
      使用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 - 使用可变参数静态表示的签名多态参数列表
      返回:
      使用Object静态表示的签名多态结果
      抛出:
      WrongMethodTypeException - 如果目标类型无法调整为调用者的符号类型描述符
      ClassCastException - 如果目标类型可以调整为调用者,但引用转换失败
      Throwable - 由底层方法抛出的任何异常将通过方法句柄调用传播
    • invokeWithArguments

      public Object invokeWithArguments(Object... arguments) throws Throwable
      执行可变参数调用,将给定数组中的参数传递给方法句柄,就好像通过一个不精确的invoke从仅提及类型Object的调用点传递一样,并且实际参数计数为参数数组的长度。

      具体来说,执行按照以下步骤进行,尽管如果JVM可以预测它们的效果,这些方法不一定会被调用。

      • 确定参数数组的长度为N。对于空引用,N=0
      • 将数组的N个元素作为逻辑参数列表收集,每个参数在静态上都被类型化为Object
      • 确定此方法句柄类型的参数计数为M
      • 确定N个参数或M个参数的一般类型TN,如果小于N,则为TN=MethodType.genericMethodType(Math.min(N, M))
      • 如果N大于M,执行以下检查和操作以缩短逻辑参数列表:
        • 检查此方法句柄是否具有可变参数性质,具有某种数组类型A[]尾部参数。如果没有,则失败并抛出WrongMethodTypeException
        • 从逻辑参数列表中收集尾部元素(共有N-M+1个)到一个类型为A[]的单个数组中,使用asType转换将每个尾部参数转换为类型A
        • 如果其中任何转换无法完成,则失败,如果任何尾部元素无法转换为A,则抛出ClassCastException,如果任何尾部元素为nullA不是引用类型,则抛出NullPointerException
        • 用类型为A[]的数组收集的逻辑参数替换为数组本身,从而将参数列表缩短为长度M。此最终参数保留静态类型A[]
        • 通过将第N个参数类型从Object更改为A[]来调整类型TN
      • 将原始目标方法句柄MH0强制转换为所需类型,如MH1 = MH0.asType(TN)
      • 将参数列表展开为N个单独的参数A0, ...
      • 在解包的参数上调用调整类型的方法句柄:MH1.invokeExact(A0, ...)。
      • 将返回值作为Object引用。

      如果目标方法句柄具有可变参数性质,并且参数列表长度超过该性质,从尾部数组参数位置开始的多余参数将被收集(如果可能,就像通过asType转换一样)到适当类型的数组中,并且将在缩短的参数列表上继续调用。通过这种方式,会统一处理会扩展到超过254个插槽的大型参数列表

      通用调用模式不同,该调用模式可以“回收”一个数组参数,直接将其传递给目标方法,这种调用模式总是创建一个新的数组参数,即使原始传递给invokeWithArguments的数组在静态上可以作为目标方法的直接参数接受。即使实际参数数量M等于参数数量N,并且最后一个参数在动态上是适当的A[]数组,它仍将被装箱为一个新的单元素数组,因为调用点在静态上将参数类型定义为Object,而不是数组类型。这不是这种方法的特殊规则,而是可变参数调用规则的常规效果。

      由于asType步骤的作用,将根据需要应用以下参数转换:

      • 引用转换
      • 拆箱
      • 扩展原始类型转换
      • 可变参数转换

      调用返回的结果如果是原始类型则被装箱,如果返回类型是void则强制为null。

      与签名多态方法invokeExactinvoke不同,invokeWithArguments可以通过核心反射API和JNI正常访问。因此,它可以用作本地或反射代码与方法句柄之间的桥梁。

      API 注意:
      此调用大致等同于以下代码:
      // 对于大型参数列表,显式调整可变参数:
      int N = (arguments == null? 0: arguments.length);
      int M = this.type.parameterCount();
      int MAX_SAFE = 127;  // 127个long需要254个插槽,这是可以接受的
      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);
        }
      } // 显式可变参数处理完成
      
      // 处理固定参数和非大型可变参数调用。
      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(或某个内部等效方法)以匹配调用者和被调用者的类型。

      如果当前方法是一个可变参数方法句柄,则参数列表转换可能涉及将多个参数转换和收集到一个数组中,如其他地方所述。在其他所有情况下,所有转换都是成对应用的,这意味着每个参数或返回值都转换为一个参数或返回值(或没有返回值)。应用的转换是通过查看旧方法句柄类型和新方法句柄类型的相应组件类型来定义的。

      T0T1为相应的新旧参数类型或旧和新返回类型。具体来说,对于某个有效的索引i,让T0=newType.parameterType(i)T1=this.type().parameterType(i)。或者,反过来对返回值进行处理,让T0=this.type().returnType()T1=newType.returnType()。如果类型相同,则新方法句柄对应的参数或返回值(如果有)不做任何更改。否则,如果可能的话,将应用以下转换之一:

      • 如果T0T1是引用,则应用到T1的强制转换。(这些类型不需要以任何特定方式相关联。这是因为null的动态值可以转换为任何引用类型。)
      • 如果T0T1是基本类型,则应用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为基本类型,则引入一个零值。
      注意: T0T1都可以被视为静态类型,因为它们都不特定对应于任何实际参数或返回值的动态类型。)

      如果所需的成对转换中的任何一个无法完成,则无法进行方法句柄转换。

      在运行时,应用于引用参数或返回值的转换可能需要额外的运行时检查,这些检查可能会失败。拆箱操作可能会失败,因为原始引用为null,导致NullPointerException。拆箱操作或引用转换也可能在引用到错误类型的对象时失败,导致ClassCastException。尽管拆箱操作可能接受多种包装类,但如果没有可用的包装类,将抛出ClassCastException

      参数:
      newType - 新方法句柄的预期类型
      返回:
      一个方法句柄,执行任何必要的参数转换后委托给this,并安排进行任何必要的返回值转换
      抛出:
      NullPointerException - 如果newType是一个null引用
      WrongMethodTypeException - 如果无法进行转换
      参见:
    • asSpreader

      public MethodHandle asSpreader(Class<?> arrayType, int arrayLength)
      生成一个数组展开方法句柄,接受一个尾随数组参数并将其元素作为位置参数展开。新方法句柄将当前方法句柄作为其目标进行调整。适配器的类型将与目标的类型相同,只是目标类型的最后arrayLength个参数将被替换为一个类型为arrayType的单个数组参数。

      如果数组元素类型与原始目标上的任何相应参数类型不同,则原始目标将被调整为直接接受数组元素,就像通过调用asType一样。

      在调用时,适配器将通过将数组的元素作为自己的参数传递给目标来替换尾随数组参数。(参数的顺序保持不变。)它们将成对转换为目标的尾随参数的类型,通过强制转换和/或拆箱。最后调用目标。适配器不会改变目标最终返回的内容。

      在调用目标之前,适配器将验证数组包含的元素数量是否正好足够提供正确的参数计数给目标方法句柄。(当需要零个元素时,数组也可以为null。)

      当调用适配器时,将查询提供的array参数的长度,就像通过array.lengtharraylength字节码一样。如果适配器接受零长度的尾随数组参数,则提供的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为负数,或者spreadArgPos的值非法(负数,或者与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调用失败
      自 JDK 9 起:
      9
      参见:
    • withVarargs

      public MethodHandle withVarargs(boolean makeVarargs)
      如果布尔标志为true,则将此方法句柄调整为可变参数,否则为固定参数。如果方法句柄已经具有正确的参数模式,则返回不变。
      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且此方法句柄没有尾随数组参数
      自 JDK 9 起:
      9
      参见:
    • asCollector

      public MethodHandle asCollector(Class<?> arrayType, int arrayLength)
      创建一个数组收集方法句柄,它接受给定数量的尾随位置参数,并将它们收集到一个数组参数中。新方法句柄将当前方法句柄作为其目标。适配器的类型将与目标的类型相同,只是单个尾随参数(通常为arrayType类型)将被arrayLength个参数替换,这些参数的类型是arrayType的元素类型。

      如果数组类型与原始目标的最终参数类型不同,则原始目标将被调整为直接接受数组类型,就像通过调用asType一样。

      调用时,适配器将其尾随的arrayLength个参数替换为一个新的类型为arrayType的数组,其元素依次包括被替换的参数。最后调用目标。目标最终返回的内容由适配器不加修改地返回。

      (当arrayLength为零时,数组也可以是共享常量。)

      注意: arrayType通常与原始目标的最后一个参数类型相同。出于对称性考虑,它是一个显式参数,也允许目标使用简单的Object作为其最后一个参数类型。)

      为了创建一个不限于特定数量收集参数的收集适配器,请使用asVarargsCollectorwithVarargs

      以下是一些数组收集方法句柄的示例:

      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 调用失败
      自 JDK 版本:
      9
      参见:
    • asVarargsCollector

      public MethodHandle asVarargsCollector(Class<?> arrayType)
      创建一个可变参数适配器,能够接受任意数量的尾随位置参数并将它们收集到一个数组参数中。

      适配器的类型和行为将与目标的类型和行为相同,只是某些invokeasType请求可能会导致尾随位置参数被收集到目标的尾随参数中。此外,适配器的最后一个参数类型将是arrayType,即使目标具有不同的最后一个参数类型。

      如果方法句柄已经是可变参数且其尾随参数类型与arrayType相同,则此转换可能返回this

      当使用invokeExact调用时,适配器将以不更改参数的方式调用目标。(注意:这种行为与固定参数收集器不同,因为它接受一个长度不确定的整个数组,而不是固定数量的参数。)

      当使用普通的、不精确的invoke调用时,如果调用者类型与适配器相同,则适配器将像使用invokeExact一样调用目标。(当类型匹配时,这是invoke的正常行为。)

      否则,如果调用者和适配器的参数数量相同,并且调用者的尾随参数类型是与适配器的尾随参数类型相同或可赋值的引用类型,则参数和返回值将成对转换,就像在固定参数方法句柄上通过asType进行的一样。

      否则,参数数量不同,或者适配器的尾随参数类型不可从对应的调用者类型进行赋值。在这种情况下,适配器将从原始尾随参数位置开始替换所有尾随参数,用一个新的类型为arrayType的数组,其元素按顺序包括被替换的参数。

      调用者类型必须提供至少足够的参数,并且是正确类型的,以满足目标对于尾随数组参数之前的位置参数的要求。因此,调用者必须至少提供N-1个参数,其中N是目标的参数数量。此外,必须存在从传入参数到目标参数的转换。与其他使用普通invoke的情况一样,如果这些基本要求没有满足,可能会抛出WrongMethodTypeException

      在所有情况下,目标最终返回的内容将由适配器原样返回。

      在最后一种情况下,就好像目标方法句柄临时使用固定参数收集器适应调用者类型所需的参数数量一样。(与asCollector一样,如果数组长度为零,可能会使用共享常量而不是新数组。如果暗示调用asCollector会抛出IllegalArgumentExceptionWrongMethodTypeException,则调用可变参数适配器必须抛出WrongMethodTypeException。)

      asType的行为也针对可变参数适配器进行了专门化,以保持普通的、不精确的invoke始终等效于调整目标类型的asType调用,然后是invokeExact。因此,可变参数适配器对asType请求的响应是构建一个固定参数收集器,仅当适配器和请求的类型在参数数量或尾随参数类型上有所不同时。结果固定参数收集器的类型将进一步调整(如果需要)以符合请求的类型,通过成对转换,就像另一个asType的应用一样。

      当通过执行ldc指令的CONSTANT_MethodHandle常量获得方法句柄,并且目标方法标记为可变参数方法(使用修饰符位0x0080),则方法句柄将接受多个参数数量,就好像方法句柄常量是通过调用asVarargsCollector创建的一样。

      要创建一个收集预定数量参数的适配器,并且其类型反映这个预定数量,使用asCollector

      除非已经明确说明,否则没有任何方法句柄转换会产生具有可变参数的新方法句柄。因此,除了asVarargsCollectorwithVarargs之外,MethodHandleMethodHandles中的所有方法将返回具有固定参数数量的方法句柄,除非在规定的情况下返回其原始操作数(例如,方法句柄自身类型的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方法或构造函数的lookup方法
      • 通过ldc指令的CONSTANT_MethodHandle解析为可变参数Java方法或构造函数
      返回:
      如果此方法句柄接受多个参数数量的普通、不精确的invoke调用,则返回true
      参见:
    • 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的未来版本可能会向字符串表示添加更多信息。因此,应用程序不应解析当前语法。)

      覆盖:
      toString 在类 Object
      返回:
      方法句柄的字符串表示