Java lambda meta factory

971 Views Asked by At
CallSite lambdaFactory = LambdaMetafactory.metafactory(
   lookup, 
   "call",
   MethodType.methodType(BiConsumer.class), 
   MethodType.methodType(void.class,Long.class), 
      lookup.findVirtual(CallClass.class, "call",       
   MethodType.methodType(void.class,Long.class)),
   MethodType.methodType(void.class));
lambdaFactory.getTarget().invoke(callId);


private void call(Long callId){
     ---
}

I am getting this exception java.lang.invoke.LambdaConversionException: Incorrect number of parameters for instance method invokeVirtual call:()void; 0 captured parameters, 0 functional interface method parameters, 0 implementation parameters

1

There are 1 best solutions below

0
On

There is almost everything wrong in this invocation:

  1. CallSite lambdaFactory = LambdaMetafactory.metafactory(
  2.   lookup,
  3.   "call", this must be the name of the interface method needed to be implemented. For BiConsumer, this is "accept".
  4.   MethodType.methodType(BiConsumer.class), this is the invokedType which must match the invoke call at (10).
  5.   MethodType.methodType(void.class,Long.class), this is the signature of the interface method to implement at the bytecode level, i.e. after type erasure. For BiConsumer, this is always MethodType.methodType(void.class, Object.class, Object.class).
  6.   lookup.findVirtual(CallClass.class, "call",
  7.     MethodType.methodType(void.class,Long.class)),
  8.   MethodType.methodType(void.class) this is the specialization of the interface or identical to (5) when the interface is not generic. In either case, the number of parameters must match with (5). For your case, you likely want match the target method, so it must be MethodType.methodType(void.class, CallClass.class, Long.class).
  9.   );
  10. lambdaFactory.getTarget().invoke(callId); the invoke call must match the invokedType signature specified in (4). The specified arguments are the values to be captured. Since you’ve chosen to implement BiConsumer, which has the same functional signature as your target method, there is no use for an additional value.

The biggest problem is the mismatch between (4) and (10), as it makes it unclear what you actually want to achieve. Do you want to capture an existing callId as invoke(callId) suggests or do you want to create a non-capturing function, as the invokedType argument and the choice of BiConsumer suggest?

For a straight-forward BiConsumer generation, the fixed code would look like:

CallSite callSite = LambdaMetafactory.metafactory(
   lookup, "accept", MethodType.methodType(BiConsumer.class), 
   MethodType.methodType(void.class, Object.class, Object.class),
   lookup.findVirtual(CallClass.class,"call", MethodType.methodType(void.class,Long.class)),
   MethodType.methodType(void.class, CallClass.class, Long.class)
);
BiConsumer<CallClass,Long> bc = (BiConsumer<CallClass, Long>)callSite.getTarget().invoke();

If you want to capture an existing callId, you would have to change the functional interface to a type not expecting a second argument. Further, you’d need an adapter, because the LambdaMetafactory expects captured arguments first and the interface method arguments afterwards. So, since this is not supported directly, the simplest solution is to generate a BiConsumer<CallClass,Long> as above, followed by Consumer<CallClass> c = cc -> bc.apply(cc, callId);, capturing the existing callId.

Only if you also have an already existing CallClass instance you want to bind, you can do it directly:

CallSite callSite = LambdaMetafactory.metafactory(
   lookup, "run", MethodType.methodType(Runnable.class, LambdaMF.class, Long.class), 
   MethodType.methodType(void.class),
   lookup.findVirtual(LambdaMF.class, "call", MethodType.methodType(void.class,Long.class)),
   MethodType.methodType(void.class)
);
Runnable r = (Runnable)callSite.getTarget().invoke(callClassInstance, callId);