Lambda Metafactory变量捕获

使用MethodHandles.LookupMethodHandleMethodType等手动创建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 f=String::length;

您的代码将无法运行。 由于你的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); }