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