Lambda Metafactory变量捕获

时间:2015-06-03 23:28:59

标签: java lambda java-8

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

例如,没有捕获:

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

及其笨重的形式,使用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,但如果有人想要捕获某些内容会怎样?

2 个答案:

答案 0 :(得分:3)

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

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

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,则后跟所有参数类型。捕获值将从左到右映射到此签名的类型,其余参数类型(如果有)对函数签名有贡献,因此必须匹配interface方法的参数类型。

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

答案 1 :(得分:2)

您的代码无法运行。由于您的fortyTwo方法不是静态的,因此您必须使用this作为MethodType.methodType(IntSupplier.class, getClass())的第三个参数来捕获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);
}