I was trying to check if it is possible to use MethodHandle::invoke or MethodHandle::invokeExact as method references for a functional interface that accepts a MethodHandle and returns a generified output.
(I know that invoke and invokeExact are signature polymorphic, hence the metafactory call in InvokeExact. However, I wanted to know if the compiler is able to elide the things that I had to do to derive a suitable version of invoke/invokeExact.)
invoke.InvokeExact0
package invoke;
import java.lang.invoke.MethodHandle;
import static java.lang.System.out;
import static java.lang.invoke.LambdaMetafactory.metafactory;
import static java.lang.invoke.MethodHandles.lookup;
import static java.lang.invoke.MethodType.methodType;
@FunctionalInterface
public interface InvokeExact0<OUTPUT> {
public OUTPUT invokeExact(MethodHandle methodHandle) throws Throwable;
public static <OUTPUT> InvokeExact0<OUTPUT> new_(InvokeExact0<OUTPUT> invokeExact) {
return invokeExact;
}
public static void main(String... arguments) throws Throwable {
out.println(
(InvokeExact0<String>) metafactory(
lookup(),
"invokeExact",
methodType(InvokeExact0.class),
methodType(
Object.class,
MethodHandle.class
),
lookup().findVirtual(
MethodHandle.class,
"invokeExact",
methodType(String.class)
),
methodType(
String.class,
MethodHandle.class
)
)
.getTarget()
.invokeExact()
);
out.println(InvokeExact0.new_(MethodHandle::invokeExact));
}
}
Result
invoke.InvokeExact0$$Lambda$1/1878246837@5ca881b5
Exception in thread "main" java.lang.BootstrapMethodError: call site initialization exception
at java.lang.invoke.CallSite.makeSite(CallSite.java:328)
at java.lang.invoke.MethodHandleNatives.linkCallSite(MethodHandleNatives.java:296)
at invoke.InvokeExact0.main(InvokeExact0.java:41)
Caused by: java.lang.invoke.LambdaConversionException: Incorrect number of parameters for instance method invokeVirtual java.lang.invoke.MethodHandle.invokeExact:(MethodH
andle)Object; 0 captured parameters, 1 functional interface method parameters, 1 implementation parameters
at java.lang.invoke.AbstractValidatingLambdaMetafactory.validateMetafactoryArgs(AbstractValidatingLambdaMetafactory.java:193)
at java.lang.invoke.LambdaMetafactory.metafactory(LambdaMetafactory.java:303)
at java.lang.invoke.CallSite.makeSite(CallSite.java:289)
... 2 more
The good news is that the metafactory approach was able to synthesize a working functional interface instance (as printed: invoke.InvokeExact0$$Lambda$1/1878246837@1be6f5c3). The bad news is that the method reference approach resulted in a LambdaConversionException, which in turn resulted in a BootstrapMethodError.
I would then like to ask how I am supposed to interpret the error details in LambdaConversionException, since the metafactory workaround exists anyway.
Your code calling metafactory
manually indeed shows that the meta factory will do it’s job if the method handle to MethodHandle.invokeExact
has the right signature. Debugging revealed that in the second case the method handle has a (MethodHandle,MethodHandle)Object
signature where it should be (MethodHandle)Object
.
While both can be created without a problem as MethodHandle.invokeExact
allows arbitrary signatures (well, it’s first argument has to be MethodHandle
, of course) the metafactory rejects the handle because it doesn’t match the functional signature as there is no second method handle in scope.
This indicates a bug in the compiler which generated the method handle constant. Generally, if you have non-reflective code, and InvokeExact0.new_(MethodHandle::invokeExact)
refers to a reflective operation but doesn’t perform the reflective operation, but get a runtime error, it indicates a compiler bug.
There is a simple work-around. While
InvokeExact0<Object> ie=MethodHandle::invokeExact;
fails with said error,
InvokeExact0<Object> ie=mh -> mh.invokeExact();
works as expected. You will need a lambda expression in place of the method reference anyway as soon as you want different return types as in
InvokeExact0<String> ie=mh -> (String)mh.invokeExact();