- java.lang.Object
-
- java.lang.invoke.MethodHandle
-
public abstract class MethodHandle extends Object
方法句柄是一个类型化的,直接可执行的对底层方法,构造函数,字段或类似低级操作的引用,具有参数或返回值的可选转换。 这些转换是相当普遍的,并且包括这样的模式为conversion , insertion , deletion ,并substitution 。方法句柄内容
根据其参数和返回类型,方法句柄是动态和强类型的。 它们不被其基础方法的名称或定义类区分。 必须使用与方法句柄自己的type descriptor相匹配的符号类型描述符来调用方法句柄。每个方法句柄通过
type
访问器报告其类型描述符。 该类型描述符是一个MethodType
对象,其结构是一系列类,其中一个是方法的返回类型(如果没有,void.class
)。方法句柄的类型控制它接受的调用类型,以及适用于它的转换种类。
方法句柄包含一对称为
invokeExact
和invoke
的特殊调用方法。 这两个调用方法都可以直接访问方法句柄的底层方法,构造函数,字段或其他操作,通过参数和返回值的转换进行修改。 两个调用者接受与方法句柄自己的类型完全匹配的调用。 普通的,不精确的调用者也接受一系列其他呼叫类型。方法句柄是不可变的,没有可见状态。 当然,它们可以绑定到显示状态的底层方法或数据。 对于Java内存模型,任何方法句柄的行为就好像它的所有(内部)字段都是最终变量。 这意味着对应用程序可见的任何方法句柄将始终完全形成。 即使在数据竞赛中通过共享变量发布方法句柄也是如此。
方法句柄不能被用户子类化。 实现可能(或可能不)创建
MethodHandle
内部子类,这可以通过Object.getClass
操作看到。 程序员不应该从其特定的类中得出关于方法句柄的结论,因为方法句柄类层次结构(如果有的话)可能会不时地或跨不同供应商的实现发生变化。方法句柄编译
命名为invokeExact
或invoke
Java方法调用表达式可以从Java源代码调用方法句柄。 从源代码的角度来看,这些方法可以接受任何参数,并将其结果转换为任何返回类型。 正式地,这是通过给出调用方法Object
返回类型和变量arityObject
参数来实现的,但是它们具有称为签名多态性的附加质量,其将该调用自由直接连接到JVM执行堆栈。像虚拟方法一样,源级调用
invokeExact
和invoke
编译为一个invokevirtual
指令。 更奇怪的是,编译器必须记录实际的参数类型,并且可能不会对参数执行方法调用转换。 相反,它必须根据自己的未转换类型生成将它们推送到堆栈的指令。 方法handle对象本身被推送到栈前的参数。 然后,编译器将生成一个invokevirtual
指令,该指令用描述参数和返回类型的符号类型描述符调用方法句柄。要发出完整的符号类型描述符,编译器还必须确定返回类型。 这是基于对方法调用表达一个演员,如果有,否则
Object
如果调用是一个表达式,否则void
如果调用的声明。 演员可能是原始类型(但不是void
)。为角情况下,uncasted
null
给出参数的象征性类型描述符java.lang.Void
。 与类型Void
的歧义是无害的,因为没有引用类型Void
除了空引用。方法句柄调用
第一次执行invokevirtual
指令时,它通过符号解析指令中的名称并验证方法调用是静态合法的。 这也适用于拨打invokeExact
和invoke
。 在这种情况下,检查编译器发出的符号类型描述符是否正确的语法,并且解析其包含的名称。 因此,只要符号类型描述符在语法上形成良好并且类型存在,则调用方法句柄的invokevirtual
指令将始终链接。当链接后执行
invokevirtual
,接收方法句柄的类型首先由JVM检查,以确保它符合符号类型描述符。 如果类型匹配失败,则意味着调用者调用的方法不会在被调用的单个方法句柄上显示。在
invokeExact
的情况下,调用的类型描述符(解析符号类型名称后)必须与接收方法句柄的方法类型完全匹配。 在普通的情况下,不精确的invoke
,解析的类型描述符必须是接收方的asType
方法的有效参数。 因此,平原invoke
比invokeExact
更invokeExact
。类型匹配后,直接调用
invokeExact
并立即调用方法句柄的底层方法(或其他行为,视情况而定)。如果调用者指定的符号类型描述符与方法句柄自己的类型完全匹配,则调用plain
invoke
工作方式与调用invokeExact
相同。 如果有类型不匹配,invoke
尝试调整接收方法句柄的类型,好像通过调用asType
,以获得一个精确可调用的方法句柄M2
。 这允许在调用者和被调用者之间进行更强大的方法类型协商。( 注意:调整后的方法句柄
M2
是不可直接观察到的,因此实现不需要实现。)调用检查
在典型的程序中,方法句柄类型匹配通常会成功。 但是,如果地址不匹配时,JVM将抛出一个WrongMethodTypeException
,直接(在的情况下invokeExact
)或间接仿佛被一个失败的调用asType
(在的情况下invoke
)。因此,在静态类型程序中可能显示为链接错误的方法类型不匹配可以在使用方法句柄的程序中显示为动态的
WrongMethodTypeException
。由于方法类型包含“live”
Class
对象,方法类型匹配同时考虑了类型名称和类加载器。 因此,即使一个方法手柄M
在一个类加载器创建L1
和使用另一L2
,方法句柄调用是类型安全的,因为调用者的符号类型描述符,如解决L2
,与最初的被叫方法的符号类型匹配描述符,解决于L1
。 决议L1
时发生M
被创建,其类型分配,而在分辨率L2
当发生invokevirtual
指令链接。除了类型描述符检查,方法句柄调用其底层方法的能力是不受限制的。 如果方法句柄是通过访问该方法的类在非公共方法上形成的,那么生成的句柄可以由任何接收到它的引用的调用者在任何地方使用。
与Core Reflection API不同,每次调用反射方法时都会检查访问权限,因此执行方法句柄访问检查when the method handle is created 。 在
ldc
(见下文)的情况下,访问检查作为链接恒定方法句柄下的常量池条目的一部分执行。因此,非公共方法的处理方式或非公共类的方法一般应保密。 它们不应该传递给不受信任的代码,除非它们来自不受信任的代码的使用将是无害的。
方法句柄创建
Java代码可以创建一个方法句柄,直接访问该代码可访问的任何方法,构造函数或字段。 这是通过一个反映出基于能力的APIMethodHandles.Lookup
完成的 。 例如,静态方法句柄可以从Lookup.findStatic
获得。 还有Core Reflection API对象的转换方法,如Lookup.unreflect
。像类和字符串一样,对应于可访问字段,方法和构造函数的方法句柄也可以直接在类文件的常量池中表示为要由
ldc
字节码加载的ldc
。 一种新型的常量存储库项,的CONSTANT_MethodHandle
,直接指的是相关的CONSTANT_Methodref
,CONSTANT_InterfaceMethodref
,或CONSTANT_Fieldref
常量存储库项。 (有关方法句柄常量的详细信息,请参阅Java虚拟机规范的第4.4.8和5.4.3.5节。)通过查找方法或通过具有变量arity修饰符位(
0x0080
)的方法或构造函数产生的方法句柄具有相应的变量特征,就像在asVarargsCollector
或withVarargs
的帮助下定义一样 。方法引用可以指静态方法或非静态方法。 在非静态情况下,方法句柄类型包括一个显式的接收器参数,在任何其他参数之前。 在方法句柄的类型中,初始的接收者参数根据初始请求方法的类来键入。 (例如,如果通过
ldc
获得非静态方法句柄,则接收器的类型是在常量池条目中命名的类。)方法句柄常量受到相同的链接时间访问检查其对应的字节码指令,如果字节码行为将抛出此类错误,则
ldc
指令将抛出相应的链接错误。作为其推论,对受保护成员的访问仅限于访问类或其子类之一的接收者,而访问类又必须是受保护成员的定义类的子类(或包兄弟)。 如果方法引用是指当前包以外的类的受保护的非静态方法或字段,则接收方参数将被缩小为访问类的类型。
当调用一个虚拟方法的方法句柄时,方法总是在接收器中查找(也就是第一个参数)。
也可以创建特定虚拟方法实现的非虚拟方法句柄。 这些不执行基于接收器类型的虚拟查找。 这种方法句柄可以模拟
invokespecial
指令对同一方法的影响。用法示例
以下是一些使用示例: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
每个调用生成一个带有下列注释中指示的符号类型描述符的单个invoke
指令。 在这些示例中,辅助方法assertEquals
被假定为在其参数上调用Objects.equals
的方法,并且断言结果为真。例外
方法invokeExact
和invoke
被声明为抛出Throwable
,这就是说方法句柄可以抛出什么没有静态限制。 由于JVM不区分被检查和未检查的异常(当然,除了它们的类之外),因此将检查的异常归因于方法句柄调用对字节码形状没有特别的影响。 但是在Java源代码中,执行方法处理调用的方法必须明确地抛出Throwable
,否则必须在本地捕获所有可抛出的值,重新抛出仅在上下文中合法的那些,并且包装非法的那些。签名多态性
invokeExact
和普通的invoke
的异常编译和链接行为由术语签名多态性引用。 如Java语言规范中定义的,签名多态方法是可以与广泛的呼叫签名和返回类型中的任何一种进行操作的方法。在源代码中,对签名多态方法的调用将编译,而不管请求的符号类型描述符如何。 像往常一样,Java编译器使用给定的符号类型描述符针对命名方法发出一个
invokevirtual
指令。 不寻常的部分是符号类型描述符是从实际的参数和返回类型派生而不是方法声明。当JVM处理包含签名多态调用的字节码时,它将成功链接任何此类调用,而不管其符号类型描述符如何。 (为了保持类型安全性,JVM将通过适当的动态类型检查来保护此类呼叫,如其他地方所述)。
字节码生成器(包括编译器后端)需要为这些方法发出未转换的符号类型描述符。 确定符号链接的工具需要接受这些未转换的描述符,而不报告链接错误。
方法手柄和Core Reflection API之间的互操作
在Lookup
API中使用工厂方法,由Core Reflection API对象表示的任何类成员都可以转换为行为上等效的方法句柄。 例如,反射方法
可以使用Lookup.unreflect
转换为方法句柄。 所得到的方法句柄通常提供对基础类成员的更直接和有效的访问。作为特殊情况,当Core Reflection API用于查看
invokeExact
的签名多态方法invokeExact
或纯invoke
时,它们显示为普通非多态方法。 它们的反射外观,如Class.getDeclaredMethod
所示 ,不受其在该API中的特殊状态的影响。 例如,Method.getModifiers
将准确报告任何类似声明的方法所需的修改位,包括在这种情况下为native
和varargs
位。与任何反映的方法一样,这些方法(反映时)可以通过
java.lang.reflect.Method.invoke
调用。 但是,这种反射调用不会导致方法句柄调用。 这样的一个调用,如果传递所需的参数(一个单一的,类型为Object[]
),将忽略该参数,并将抛出一个UnsupportedOperationException
。由于
invokevirtual
指令可以在任何符号类型描述符下本地调用方法句柄,所以这种反射视图与通过字节码的这些方法的正常呈现相冲突。 因此,当Class.getDeclaredMethod
反映出来时,这两种原生方法可能只被视为占位符。为了获得特定类型描述符的调用者方法,请使用
MethodHandles.exactInvoker
或MethodHandles.invoker
。 对于任何指定的类型描述符,Lookup.findVirtual
API还能够返回一个方法句柄来调用invokeExact
或者简单的invoke
。方法句柄和Java泛型之间的互操作
可以使用Java通用类型声明的方法,构造函数或字段获取方法句柄。 与Core Reflection API一样,方法句柄的类型将由源级类型的擦除构成。 调用方法句柄时,其参数或返回值转换类型的类型可能是通用类型或类型实例。 如果发生这种情况,编译器将在构建invokevirtual
指令的符号类型描述符时,通过其擦除来替换这些类型。方法句柄在Java参数化(通用)类型方面并不表示其类似函数的类型,因为类函数类型和参数化Java类型之间存在三个不匹配。
- 方法类型涵盖所有可能的概率,从无参数到最多允许参数的maximum number 。 泛型不是可变的,所以不能代表这一点。
- 方法类型可以指定原始类型的参数,哪些Java通用类型不能覆盖。
- 方法手柄(组合器)的高阶函数通常在广泛的函数类型(包括多个特征的函数类型)中是通用的。 使用Java类型参数来表示这种通用性是不可能的。
Arity limit
JVM对任何类型的所有方法和构造函数都施加255个堆栈参数的绝对限制。 在某些情况下,这个限制可能会更具限制性:- A
long
或double
参数计数(用于特性限制)作为两个参数插槽。 - 非静态方法为调用该方法的对象消耗额外的参数。
- 构造函数为正在构造的对象消耗额外的参数。
- 由于方法句柄
invoke
方法(或其他签名 - 多态方法)是非虚拟的,除了任何非虚拟接收方对象之外,它还为方法句柄本身消耗额外的参数。
IllegalArgumentException
。 特别地,方法句柄的类型不能具有最大255的精确度。- 从以下版本开始:
- 1.7
- 另请参见:
-
MethodType
,MethodHandles
-
-
方法摘要
所有方法 接口方法 具体的方法 Modifier and Type 方法 描述 MethodHandle
asCollector(int collectArgPos, Class<?> arrayType, int arrayLength)
创建一个 数组收集方法句柄,它接收从给定位置开始的给定数量的位置参数,并将它们收集到数组参数中。MethodHandle
asCollector(Class<?> arrayType, int arrayLength)
使 数组收集方法句柄接受给定数量的尾随位置参数并将其收集到数组参数中。MethodHandle
asFixedArity()
创建一个 固定的arity方法句柄,否则相当于当前的方法句柄。MethodHandle
asSpreader(int spreadArgPos, Class<?> arrayType, int arrayLength)
创建一个 数组扩展方法句柄,它可以在给定的位置接受一个数组参数,并将其元素作为位置参数扩展代替数组。MethodHandle
asSpreader(Class<?> arrayType, int arrayLength)
创建一个 数组扩展方法句柄,它接受一个尾随数组参数,并将其元素作为位置参数传播。MethodHandle
asType(MethodType newType)
生成一个适配器方法句柄,该句柄将当前方法句柄的类型适配为新类型。MethodHandle
asVarargsCollector(Class<?> arrayType)
创建一个 可变的arity适配器,它可以接受任意数量的尾随位置参数并将其收集到数组参数中。MethodHandle
bindTo(Object x)
将值x
绑定到方法句柄的第一个参数,而不调用它。Object
invoke(Object... args)
调用方法句柄,允许任何调用者类型描述符,以及可选地对参数和返回值执行转换。Object
invokeExact(Object... args)
调用方法句柄,允许任何调用者类型描述符,但需要确切的类型匹配。Object
invokeWithArguments(Object... arguments)
执行可变元数调用,传递的参数在给定列表的方法处理,就好像通过不精确invoke
从呼叫位点,其仅提到类型Object
,且其元数是参数列表的长度。Object
invokeWithArguments(List<?> arguments)
boolean
isVarargsCollector()
确定此方法是否支持 variable arity调用。String
toString()
返回方法句柄的字符串表示形式,从字符串"MethodHandle"
开始,以方法句柄类型的字符串表示形式结束。MethodType
type()
报告此方法句柄的类型。MethodHandle
withVarargs(boolean makeVarargs)
如果布尔标志为真,则将此方法的句柄改为 variable arity ,否则为 fixed arity 。
-
-
-
方法详细信息
-
type
public MethodType type()
报告此方法句柄的类型。 通过invokeExact
每次调用此方法句柄必须与此类型完全匹配。- 结果
- 方法句柄类型
-
invokeExact
public final Object invokeExact(Object... args) throws Throwable
调用方法句柄,允许任何调用者类型描述符,但需要确切的类型匹配。invokeExact
调用站点上的符号类型描述符必须与此方法句柄的type
完全匹配。 参数或返回值不允许转换。当通过Core Reflection 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
方法的有效参数。 特别地,如果被调用者不是variable arity collector ,则调用者必须指定与被调用者的类型相同的参数。当通过Core Reflection API观察此方法时,它将显示为单个本机方法,获取对象数组并返回一个对象。 如果通过
java.lang.reflect.Method.invoke
直接通过JNI或间接通过Lookup.unreflect
直接调用此本机方法,则会抛出一个UnsupportedOperationException
。- 参数
-
args
- 使用varargs静态表示的签名 - 多态参数列表 - 结果
-
签名多态结果,静态表示为
Object
- 异常
-
WrongMethodTypeException
- 如果目标的类型无法调整到调用者的符号类型描述符 -
ClassCastException
- 如果目标的类型可以调整到调用者,但参考转换失败 -
Throwable
- 底层方法抛出的东西通过方法句柄调用传播不变
-
invokeWithArguments
public Object invokeWithArguments(Object... arguments) throws Throwable
执行变量arity调用,将给定列表中的参数传递给方法句柄,好像通过来自调用站点的不精确的invoke
,其中仅提及类型为Object
,其特征是参数列表的长度。具体来说,执行如下,通过以下步骤进行,尽管如果JVM可以预测它们的效果,则不保证调用这些方法。
- 确定参数数组的长度为
N
。 对于null引用,N=0
。 - 确定
N
参数的一般类型TN
,如TN=MethodType.genericMethodType(N)
。 - 将原始目标方法手柄
MH0
为所需类型,如MH1 = MH0.asType(TN)
。 - 将数组扩展为
N
独立参数A0, ...
。 - 调用解压缩参数的类型调整方法句柄:MH1.invokeExact(A0,...)。
- 取返回值作为
Object
参考。
由于
asType
步骤的操作,必要时将应用以下参数转换:- 参考铸造
- 拆箱
- 拓宽原始转换
如果调用返回的结果是原始的,则调用返回的结果为boxed,如果返回类型为void,则强制为null。
此呼叫等同于以下代码:
MethodHandle invoker = MethodHandles.spreadInvoker(this.type(), 0); Object result = invoker.invokeExact(this, arguments);
与签名多态方法
invokeExact
和invoke
,invokeWithArguments
可以通过Core Reflection API和JNI正常访问。 因此,它可以用作本机或反射代码和方法句柄之间的桥梁。- 参数
-
arguments
- 传递给目标的参数 - 结果
- 目标返回的结果
- 异常
-
ClassCastException
- 如果参数无法通过引用转换转换 -
WrongMethodTypeException
- 如果目标的类型无法调整为采用给定数量的Object
参数 -
Throwable
- 目标方法调用抛出的东西 - 另请参见:
-
MethodHandles.spreadInvoker(java.lang.invoke.MethodType, int)
- 确定参数数组的长度为
-
invokeWithArguments
public Object invokeWithArguments(List<?> arguments) throws Throwable
执行变量arity调用,将给定数组中的参数传递给方法句柄,好像通过来自调用站点的不精确的invoke
,其中仅提及类型为Object
,其特征是参数数组的长度。此方法也等效于以下代码:
invokeWithArguments(arguments.toArray())
- 参数
-
arguments
- 传递给目标的参数 - 结果
- 目标返回的结果
- 异常
-
NullPointerException
- 如果arguments
是空引用 -
ClassCastException
- 如果参数无法通过引用转换转换 -
WrongMethodTypeException
- 如果目标的类型不能被调整为采用给定数量的Object
参数 -
Throwable
- 目标方法调用抛出的东西
-
asType
public MethodHandle asType(MethodType newType)
生成一个适配器方法句柄,该句柄将当前方法句柄的类型适配为新类型。 生成的方法句柄保证报告一个等于所需新类型的类型。如果原始类型和新类型相同,则返回
this
。新方法句柄被调用时,将执行以下步骤:
- 转换传入参数列表以匹配原始方法句柄的参数列表。
- 在转换的参数列表中调用原始方法句柄。
- 将原始方法句柄返回的任何结果转换为新方法句柄的返回类型。
这种方法提供了
invokeExact
与普通,不准确的invoke
之间的关键行为差异。 当调用者的类型描述符与被调用者完全匹配时,两种方法执行相同的步骤,但是当类型不同时,普通的invoke
也会调用asType
(或一些内部等效的)来匹配调用者和被调用者的类型。如果当前方法是一个可变的arity方法,那么handle参数列表转换可能涉及将数个参数转换并收集到一个数组中,如described elsewhere 。 在所有其他情况下,所有转换都将成对应用,这意味着每个参数或返回值都将转换为一个参数或返回值(或不返回值)。 应用的转换通过查看旧方法和新方法句柄类型的相应组件类型进行定义。
令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为空, T1为引用,则引入空值。
- 如果返回类型T0为空, T1为原语,则引入零值。
如果无法进行任何一个所需的成对转换,则无法进行方法句柄转换。
在运行时,应用于引用参数或返回值的转换可能需要额外的运行时检查,这可能会失败。 拆箱操作可能会失败,因为原始引用为null,导致
NullPointerException
。 一个拆箱操作或一个引用转换也可能引用一个错误类型的对象,导致一个ClassCastException
。 虽然拆箱操作可能会接受几种包装,但是如果没有可用的话,那么ClassCastException
将被抛出。- 参数
-
newType
- 新方法句柄的预期类型 - 结果
-
一个方法句柄,在执行任何必要的参数转换后委托给
this
,并安排任何必要的返回值转换 - 异常
-
NullPointerException
- 如果newType
是空引用 -
WrongMethodTypeException
- 如果不能进行转换 - 另请参见:
-
MethodHandles.explicitCastArguments(java.lang.invoke.MethodHandle, java.lang.invoke.MethodType)
-
asSpreader
public MethodHandle asSpreader(Class<?> arrayType, int arrayLength)
创建一个数组扩展方法句柄,它接受一个尾随数组参数,并将其元素作为位置参数传播。 新的方法句柄适应当前方法句柄的目标 。 适配器的类型将与目标的类型相同,但目标类型的最终arrayLength
参数被替换为arrayType
型的单个数组参数。如果数组元素类型与原始目标上的任何相应参数类型不同,则原始目标适用于直接获取数组元素,就像通过调用
asType
一样 。调用时,适配器将数组元素的尾随数组参数替换为每个对象的自身参数。 (参数的顺序保留。)它们通过转换和/或取消装箱成对转换为目标的尾随参数的类型。 最后调用目标。 目标最终返回的内容不会被适配器返回。
在调用目标之前,适配器会验证数组是否包含足够的元素,以便为目标方法句柄提供正确的参数计数。 200的X-454545新新新新新200新新200新新200新新200新新新200新新200新新200新新200新新200新新新200新新新200新新200新新新新新新评名新旗
如果调用适配器时,提供的数组参数不具有正确数量的元素,则适配器将抛出一个
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 ? null : 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
为负,或者如果生成的方法句柄的类型将具有 too many parameters -
WrongMethodTypeException
- 如果隐含的asType
调用失败 - 另请参见:
-
asCollector(java.lang.Class<?>, int)
-
asSpreader
public MethodHandle asSpreader(int spreadArgPos, Class<?> arrayType, int arrayLength)
创建一个数组扩展方法句柄,它可以在给定的位置接受一个数组参数,并将其元素作为位置参数扩展代替数组。 新的方法句柄适应当前方法句柄的目标 。 适配器的类型将与目标的类型相同,不同之处在于从零spreadArgPos
位置spreadArgPos
开始的目标类型的arrayLength
参数由spreadArgPos
的单个数组参数arrayType
。该方法的行为非常像
asSpreader(Class, int)
,但是接受一个额外的spreadArgPos
参数来指示参数列表中哪个位置应该发生扩展。- API Note:
-
例:
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超过参数数量一起),或如果结果方法句柄的类型将具有 too many parameters -
WrongMethodTypeException
- 如果隐含的asType
调用失败 - 从以下版本开始:
- 9
- 另请参见:
-
asSpreader(Class, int)
-
withVarargs
public MethodHandle withVarargs(boolean makeVarargs)
如果布尔标志为真,则将此方法的句柄改为variable arity ,否则为fixed arity 。 如果方法句柄已经是正确的arity模式,则返回不变。- API Note:
-
当适应可能是可变的方法句柄时,这种方法有时是有用的,以确保当且只有原始句柄时,生成的适配器也是可变的。 例如,该代码将句柄
mh
的第一个参数更改为int
而不会影响其可变的arity属性:mh.asType(mh.type().changeParameterType(0,int.class)) .withVarargs(mh.isVarargsCollector())
- 参数
-
makeVarargs
- 如果返回方法句柄应具有变量arity行为,makeVarargs
true - 结果
- 相同类型的方法句柄,具有可能调整的变量特征行为
- 异常
-
IllegalArgumentException
- 如果makeVarargs
为真,并且此方法句柄没有尾数组参数 - 从以下版本开始:
- 9
- 另请参见:
-
asVarargsCollector(java.lang.Class<?>)
,asFixedArity()
-
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));
注意:即使原始目标方法句柄是,生成的适配器从不是variable-arity method handle 。
- 参数
-
arrayType
- 通常是Object[]
,将会收集参数的数组参数的类型 -
arrayLength
- 要收集到新数组参数中的参数数 - 结果
- 一个新的方法句柄,在调用原始方法句柄之前,将一些尾随参数收集到数组中
- 异常
-
NullPointerException
- 如果arrayType
是空引用 -
IllegalArgumentException
- 如果arrayType
不是数组类型,或者arrayType
不能分配给该方法句柄的尾随参数类型,或者arrayLength
不是合法的数组大小,或者结果方法句柄的类型将具有 too many parameters -
WrongMethodTypeException
- 如果隐含的asType
调用失败 - 另请参见:
-
asSpreader(java.lang.Class<?>, int)
,asVarargsCollector(java.lang.Class<?>)
-
asCollector
public MethodHandle asCollector(int collectArgPos, Class<?> arrayType, int arrayLength)
创建一个数组收集方法句柄,它接收从给定位置开始的给定数量的位置参数,并将它们收集到数组参数中。 新的方法句柄适应当前方法句柄的目标 。 适配器的类型与目标的类型相同,不同之处在于collectArgPos
(通常为arrayType
)所示位置的参数被arrayLength
的元素类型为arrayType
参数所取代。此方法的行为非常像
asCollector(Class, int)
,但其不同之处在于其collectArgPos
参数表示应在哪个位置参数列表参数中收集。 此索引为零。- API Note:
-
例子:
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());
注意:即使原始的目标方法句柄是,生成的适配器从不是variable-arity method handle 。
- 参数
-
collectArgPos
- 参数列表中从零开始收集的位置。 -
arrayType
- 通常是Object[]
,将收集参数的数组参数的类型 -
arrayLength
- 要收集到新数组参数中的参数数 - 结果
- 在调用原始方法句柄之前,将一些参数收集到数组中的新方法句柄
- 异常
-
NullPointerException
- 如果arrayType
是空引用 -
IllegalArgumentException
- 如果arrayType
不是数组类型,或arrayType
不能分配给该方法句柄的数组参数类型,或者arrayLength
不是合法的数组大小,或者collectArgPos
具有非法值(负数或大于参数数),或结果方法句柄的类型将具有 too many parameters -
WrongMethodTypeException
- 如果隐含的asType
调用失败 - 从以下版本开始:
- 9
- 另请参见:
-
asCollector(Class, int)
-
asVarargsCollector
public MethodHandle asVarargsCollector(Class<?> arrayType)
创建一个可变的arity适配器,它可以接受任意数量的尾随位置参数并将其收集到数组参数中。适配器的类型和行为将与目标的类型和行为相同,但某些
invoke
和asType
请求可能会导致尾随的位置参数被收集到目标的尾随参数中。 此外,适配器的最后一个参数类型将为arrayType
,即使目标具有不同的最后一个参数类型。这种转化可以返回
this
,如果该方法是手柄元数可变的已和它的尾部参数类型是相同的arrayType
。当用
invokeExact
调用时,适配器调用目标,而不改变参数。 ( 注意:此行为与fixed arity collector不同,因为它接受一个不确定长度的整个数组,而不是固定数量的参数)。当使用普通的,不准确的
invoke
调用时,如果调用者类型与适配器相同,则适配器会像invokeExact
一样调用目标。 (这是类型匹配时invoke
的正常行为。)否则,如果调用者和适配器类型相同,并且调用者的尾部参数类型是与适配器的尾部参数类型相同或可分配的引用类型,则参数和返回值将成对转换,如同通过
asType
在固定的方法手柄上。否则,电平不一致,或适配器的尾随参数类型不能从相应的呼叫者类型分配。 在这种情况下,适配器将从原始的尾随参数位置
arrayType
新的arrayType
型arrayType
,其元素包括(按顺序)替换的参数。调用者类型必须提供足够的参数和正确类型,以满足目标对尾随数组参数之前位置参数的要求。 因此,呼叫者必须至少提供
N-1
参数,其中N
是目标的真实性。 此外,必须存在从传入参数到目标参数的转换。 与普通invoke
其他用途invoke
,如果这些基本要求不能满足,可能会抛出一个WrongMethodTypeException
。在所有情况下,最终返回的目标将由适配器保持不变。
在最后的情况下,正好像目标方法句柄暂时适应了呼叫者类型所要求的一个fixed arity collector 。 (与
asCollector
,如果数组长度为零,则可以使用共享常量而不是新数组,如果对asCollector
的隐含调用将抛出一个IllegalArgumentException
或WrongMethodTypeException
,则对变量arity适配器的调用必须抛出WrongMethodTypeException
))asType
的行为也是专门针对可变局域适配器,维持不变量,平坦,不精确的invoke
总是相当于一个asType
调用调整目标类型,其次是invokeExact
。 因此,当且仅当适配器和请求的类型在正态分布或尾随参数类型不同时,可变地区适配器才能响应asType
请求。 所得到的固定收集器的类型通过成对转换进一步调整(如果需要)到所请求的类型,好像另外应用了asType
。当通过执行
CONSTANT_MethodHandle
常量的ldc
指令获取方法句柄,并将目标方法标记为可变方法(具有修饰符位0x0080
)时,方法句柄将接受多个特征,就好像方法句柄常数为通过调用asVarargsCollector
。为了创建收集预定数量的参数并且其类型反映该预定数量的收集适配器,代之以使用
asCollector
。没有方法句柄转换可以产生具有可变原型的新方法句柄,除非它们被记录为这样做。 因此,除了
asVarargsCollector
和withVarargs
,在所有方法MethodHandle
和MethodHandles
将返回一个方法手柄固定元数,除非他们被指定为返回原来的操作数(例如,案件asType
的方法处理自己的类型)。调用
asVarargsCollector
的方法句柄已经是可变的,将产生一个具有相同类型和行为的方法句柄。 它可能(或可能不)返回原始变量arity方法句柄。这是一个例子,一个列表制作变量arity方法句柄:
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规则的动态类型变体。 在这两种情况下,调用变量arity方法或方法句柄都可以传递零个或多个位置参数,否则可以传递任何长度的预先收集的数组。 用户应该意识到最终参数的特殊角色,以及类型匹配对最终参数的影响,该参数决定了单个尾随参数是否被解释为数组的整个数组或单个元素。集。 请注意,尾随参数的动态类型对此决定没有影响,只是调用站点的符号类型描述符和方法句柄的类型描述符之间的比较。)
- 参数
-
arrayType
- 通常是Object[]
,将收集参数的数组参数的类型 - 结果
- 一个新的方法句柄,可以在调用原始方法句柄之前收集任意数量的尾随参数到数组中
- 异常
-
NullPointerException
- 如果arrayType
是空引用 -
IllegalArgumentException
- 如果arrayType
不是数组类型或arrayType
不能分配给该方法句柄的尾随参数类型 - 另请参见:
-
asCollector(java.lang.Class<?>, int)
,isVarargsCollector()
,withVarargs(boolean)
,asFixedArity()
-
isVarargsCollector
public boolean isVarargsCollector()
确定此方法是否支持variable arity调用。 这种方法处理来自以下来源:- 致电asVarargsCollector
- 调用一个lookup method ,它解析为一个可变的arity Java方法或构造函数
- 一个
ldc
一个的指令CONSTANT_MethodHandle
其解析为一个可变参数数量的Java方法或构造
- 结果
-
如果这个方法句柄接受多于一个的纯粹的,不准确的
invoke
调用,invoke
- 另请参见:
-
asVarargsCollector(java.lang.Class<?>)
,asFixedArity()
-
asFixedArity
public MethodHandle asFixedArity()
创建一个固定的arity方法句柄,否则相当于当前的方法句柄。如果当前方法句柄不是variable arity ,则返回当前的方法句柄。 即使当前的方法句柄不能是有效的输入到
asVarargsCollector
。否则,所得到的固定方法句柄具有与当前方法句柄相同的类型和行为,但
isVarargsCollector
将为false。 固定方法句柄可以(或可能不是)是asVarargsCollector
的先前参数。这是一个例子,一个列表制作变量arity方法句柄:
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());
- 结果
- 一个只接受固定数量参数的新方法句柄
- 另请参见:
-
asVarargsCollector(java.lang.Class<?>)
,isVarargsCollector()
,withVarargs(boolean)
-
bindTo
public MethodHandle bindTo(Object x)
将值x
绑定到方法句柄的第一个参数,而不调用它。 新的方法句柄通过将其绑定到给定的参数来适配当前方法句柄作为其目标 。 绑定句柄的类型将与目标的类型相同,不同之处在于单个引用参考参数将被省略。当被调用时,绑定的句柄将给定值
x
作为新的引导参数插入目标。 其他论点也不变。 目标最终返回的结果由绑定句柄返回。参考
x
必须可转换为目标的第一个参数类型。注意:由于方法句柄是不可变的,所以目标方法句柄保留其原始类型和行为。
注意:即使原始目标方法句柄是,生成的适配器从不是一个variable-arity method handle 。
- 参数
-
x
- 绑定到目标的第一个参数的值 - 结果
- 一个新的方法句柄,在调用原始方法句柄之前,将给定值添加到传入参数列表
- 异常
-
IllegalArgumentException
- 如果目标不具有引用类型的引导参数类型 -
ClassCastException
- 如果x
无法转换为目标的引导参数类型 - 另请参见:
-
MethodHandles.insertArguments(java.lang.invoke.MethodHandle, int, java.lang.Object...)
-
-