Lambda Metafactory变量捕获
使用MethodHandles.Lookup
, MethodHandle
, MethodType
等手动创建lambda时,如何实现变量捕获?
例如,没有捕获:
public IntSupplier foo() { return this::fortyTwo; } /** * Would not normally be virtual, but oh well. */ public int fortyTwo() { return 42; }
和它的笨重forms,使用java.lang.invoke
东西:
public IntSupplier foo() { MethodHandles.Lookup lookup = MethodHandles.lookup(); MethodType methodType = MethodType.methodType(int.class), lambdaType = MethodType.methodType(IntSupplier.class); MethodHandle methodHandle = lookup.findVirtual(getClass(), "fortyTwo", methodType); CallSite callSite = LambdaMetafactory.metafactory(lookup, "getAsInt", lambdaType, methodType, methodHandle, methodType); return (IntSupplier) callSite.getTarget().invokeExact(); } /** * Would not normally be virtual, but oh well. */ public int fortyTwo() { return 42; }
会返回一个简单的,毫无意义的IntSupplier
,在调用时会返回42
,但是如果想要捕获某些内容会怎么样?
引导方法的第三个参数(名为lambdaType
)是关联的invokedynamic
指令的调用类型 (通常由JVM填充)。 它的语义是由bootstrap方法定义的,在LambdaMetaFactory
的情况下,它将函数接口指定为返回类型(要构造的对象的类型),并将要捕获的值指定为参数类型(要使用的值的类型)构造一个lambda实例)。
因此,为了捕获this
,您必须将此类型添加到您调用的类型,并将this
作为参数传递给invokeExact
调用:
public class Test { public static void main(String... arg) throws Throwable { System.out.println(new Test().foo().getAsInt()); } public IntSupplier foo() throws Throwable { MethodHandles.Lookup lookup = MethodHandles.lookup(); MethodType methodType = MethodType.methodType(int.class), invokedType = MethodType.methodType(IntSupplier.class, Test.class); MethodHandle methodHandle = lookup.findVirtual(getClass(), "fortyTwo", methodType); CallSite callSite = LambdaMetafactory.metafactory(lookup, "getAsInt", invokedType, methodType, methodHandle, methodType); return (IntSupplier) callSite.getTarget().invokeExact(this); } public int fortyTwo() { return 42; } }
如果要捕获更多值,则必须按正确的顺序将它们添加到签名中。 例如,要捕获另一个int
值:
public class Test { public static void main(String... arg) throws Throwable { System.out.println(new Test().foo(100).getAsInt()); } public IntSupplier foo(int capture) throws Throwable { MethodHandles.Lookup lookup = MethodHandles.lookup(); MethodType methodType = MethodType.methodType(int.class, int.class), functionType = MethodType.methodType(int.class), invokedType = MethodType.methodType(IntSupplier.class, Test.class, int.class); MethodHandle methodHandle=lookup.findVirtual(getClass(),"addFortyTwo",methodType); CallSite callSite = LambdaMetafactory.metafactory(lookup, "getAsInt", invokedType, functionType, methodHandle, functionType); return (IntSupplier) callSite.getTarget().invokeExact(this, capture); } public int addFortyTwo(int valueToAdd) { return 42+valueToAdd; } }
目标方法将具有this
类型组成的签名(如果不是static
,后跟所有参数类型。 捕获值将从左到右映射到此签名的类型,其余参数类型(如果有)对function签名有贡献,因此必须匹配interface
方法的参数类型。
这意味着当没有捕获的值并且目标方法不是static
,方法接收器类型可能与第一种类型的function签名相关联,如ToIntFunction
。
您的代码将无法运行。 由于你的fortyTwo
方法不是静态的,你必须通过使用MethodType.methodType(IntSupplier.class, getClass())
作为metafactory
的第三个参数来metafactory
,然后将this
作为参数传递给invokeExact
。
下面是使用静态方法捕获字符串以使事情更简单的示例:
public static int len(String s) { return s.length(); } public IntSupplier supplyLength(String capture) throws Throwable { MethodHandles.Lookup lookup = MethodHandles.lookup(); CallSite callSite = LambdaMetafactory.metafactory( lookup, "getAsInt", methodType(IntSupplier.class, String.class), methodType(int.class), lookup.findStatic(getClass(), "len", methodType(int.class, String.class)), methodType(int.class) ); return (IntSupplier) callSite.getTarget().invoke(capture); }