Reputation: 321
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?
Upvotes: 1
Views: 401
Reputation: 298143
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
.
Upvotes: 7
Reputation: 33865
Direct method handles are those that directly refer to Java methods, however, your method handle does not, because you use an adapter:
constructor = constructor.asSpreader(Object[].class, 2);
As such, it can not be used in combination with LambdaMetafactory
.
LMF is a runtime API that serves as the implementation for the lambda and method reference language features. It is not really meant to be used directly, so there are some sharp edges as you've found out.
For indirect method handles, your options are either MethodHandleProxies
:
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);
Function<Object[], R> f = (Function<Object[], R>) MethodHandleProxies.asInterfaceInstance(Function.class, constructor);
Using a lambda expression to wrap the method handle:
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);
Function<Object[], R> f = args -> {
try {
return (R) constructor.invokeExact(args);
catch (Throwable t) {
throw new IllegalStateException("Should no happen", t);
}
};
Or you could spin your own class that implements the interface and wraps the method handle using a bytecode library like ASM.
Upvotes: 6