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?
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
.