javareflectionrecordmethodhandlelambda-metafactory

MethodHandle cannot be cracked when using LambdaMetafactory?


I want to convert a Record's constructor to a Function<Object[], T> using lambdametafactory (T is a generic type), here are my codes:

public record R(
        String a,
        String b
) {
}

private static void testRecord() throws Throwable {
    MethodHandles.Lookup lookup = MethodHandles.lookup();
    MethodHandle constructor = lookup.findConstructor(R.class, MethodType.methodType(void.class, String.class, String.class));
    constructor = constructor.asSpreader(Object[].class, 2);
    R r = (R) constructor.invokeExact(new Object[]{"a", "b"});
    System.out.println(r.a());
    System.out.println(r.b());
    MethodType methodType = constructor.type();
    CallSite callSite = LambdaMetafactory.metafactory(lookup,
            "apply",
            MethodType.methodType(Function.class),
            methodType.erase(),
            constructor,
            methodType);
    Function<Object[], R> f = (Function<Object[], R>) callSite.getTarget().invokeExact();
    R apply = f.apply(new Object[]{"a", "b"});
    System.out.println(apply.a());
    System.out.println(apply.b());
} 

When using constructor.invokeExact() method, the record could be instantiated successfully, but the CallSite couldn't be generated by LambdaMetafactory.metafactory() method because of following errors:

Exception in thread "main" java.lang.invoke.LambdaConversionException: MethodHandle(Object[])R is not direct or cannot be cracked
    at java.base/java.lang.invoke.AbstractValidatingLambdaMetafactory.<init>(AbstractValidatingLambdaMetafactory.java:143)
    at java.base/java.lang.invoke.InnerClassLambdaMetafactory.<init>(InnerClassLambdaMetafactory.java:168)
    at java.base/java.lang.invoke.LambdaMetafactory.metafactory(LambdaMetafactory.java:336)

How can I fix this?


Solution

  • There is a trick to circumvent this restriction. Instead of creating a Function bound to the constructor handle, create a Function bound to the invokeExact method of the handle, to be invoked on the specific handle:

    private static void testRecord() throws Throwable {
        MethodHandles.Lookup lookup = MethodHandles.lookup();
        MethodHandle constructor = lookup.findConstructor(R.class,
            MethodType.methodType(void.class, String.class, String.class));
        constructor = constructor.asSpreader(Object[].class, 2);
        R r = (R)constructor.invokeExact(new Object[] { "a", "b" });
        System.out.println(r.a());
        System.out.println(r.b());
        MethodType methodType = constructor.type();
        CallSite callSite = LambdaMetafactory.metafactory(lookup,
            "apply",
            MethodType.methodType(Function.class, MethodHandle.class),
            methodType.erase(),
            MethodHandles.exactInvoker(methodType), // target MethodHandle.invokeExact
            methodType);
        Function<Object[], R> f = (Function<Object[], R>)
            callSite.getTarget().invokeExact(constructor); // bind to <constructor>
        R apply = f.apply(new Object[] { "a", "b" });
        System.out.println(apply.a());
        System.out.println(apply.b());
    }
    

    The crucial part is to specify MethodHandles.exactInvoker(methodType) which creates an unbound handle to the invokeExact method of a handle like ours, i.e. having the type (MethodHandle,Object[])R. Then, specify the actual handle as argument to the instantiation, to be captured by the Function.