Lambda Metafactory变量捕获

Mon*_*nad 7 java lambda java-8

使用MethodHandles.Lookup,MethodHandles,MethodTypes等手动创建lambda时,如何实现变量捕获?

例如,没有捕获:

public IntSupplier foo() {
    return this::fortyTwo;
}
/**
 *  Would not normally be virtual, but oh well.
 */
public int fortyTwo() {
    return 42;
}
Run Code Online (Sandbox Code Playgroud)

和它的笨拙形式,使用的东西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;
}
Run Code Online (Sandbox Code Playgroud)

会返回一个简单的,无意义的IntSupplier,42在调用时返回,但如果有人想要捕获什么呢?

Hol*_*ger 5

bootstrap 方法的第三个参数(您将其命名为lambdaType)是关联指令的调用类型invokedynamic(通常由 JVM 填充)。It\xe2\x80\x99s 语义由 bootstrap 方法定义,在 的情况下LambdaMetaFactory,它将函数接口指定为返回类型(要构造的对象的类型),并将要捕获的值指定为参数类型(构造 lambda 实例时要使用的值)。

\n\n

因此,为了捕获this,您必须将 的类型添加this到调用的类型中,并this作为参数传递给调用invokeExact

\n\n
public class Test {\n    public static void main(String... arg) throws Throwable {\n        System.out.println(new Test().foo().getAsInt());\n    }\n    public IntSupplier foo() throws Throwable {\n        MethodHandles.Lookup lookup = MethodHandles.lookup();\n        MethodType methodType  = MethodType.methodType(int.class),\n                   invokedType = MethodType.methodType(IntSupplier.class, Test.class);\n        MethodHandle methodHandle = lookup.findVirtual(getClass(), "fortyTwo", methodType);\n        CallSite callSite = LambdaMetafactory.metafactory(lookup, "getAsInt",\n            invokedType, methodType, methodHandle, methodType);\n        return (IntSupplier) callSite.getTarget().invokeExact(this);\n    }\n    public int fortyTwo() {\n        return 42;\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

如果您想捕获更多值,则必须以正确的顺序将它们添加到签名中。例如,要捕获另一个int值:

\n\n
public class Test {\n    public static void main(String... arg) throws Throwable {\n        System.out.println(new Test().foo(100).getAsInt());\n    }\n    public IntSupplier foo(int capture) throws Throwable {\n        MethodHandles.Lookup lookup = MethodHandles.lookup();\n        MethodType methodType = MethodType.methodType(int.class, int.class),\n            functionType = MethodType.methodType(int.class),\n            invokedType = MethodType.methodType(IntSupplier.class, Test.class, int.class);\n        MethodHandle methodHandle=lookup.findVirtual(getClass(),"addFortyTwo",methodType);\n        CallSite callSite = LambdaMetafactory.metafactory(lookup, "getAsInt",\n            invokedType, functionType, methodHandle, functionType);\n        return (IntSupplier) callSite.getTarget().invokeExact(this, capture);\n    }\n    public int addFortyTwo(int valueToAdd) {\n        return 42+valueToAdd;\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

this目标方法将具有由类型(如果不是)组成的签名static,后跟所有参数类型。捕获值将从左到右映射到此签名\xe2\x80\x99s类型,其余参数类型(如果有)有助于功能签名,因此必须匹配方法\ interfacexe2\x80\x99s参数类型。

\n\n

这意味着当没有捕获的值并且目标方法不是 时static,方法接收者类型可能会与函数签名的第一种类型相关联,如 中所示ToIntFunction<String> f=String::length;

\n