BootstrapMethodError caused by LambdaConversionExc

2019-05-15 23:20发布

问题:

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.

回答1:

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();