LambdaMetaFactory with concrete implementation of generic type

  • A+
Category:Languages

I am trying to use Java's LambdaMetaFactory to dynamically implement a generic lambda, Handler<RoutingContext>:

public class RoutingContext {     // ... }  @FunctionalInterface public interface Handler<X> {     public void handle(X arg); }  public class HomeHandler extends Handler<RoutingContext> {     @Override     public void handle(RoutingContext ctx) {         // ...     } } 

Here is my attempt at LambdaMetaFactory:

try {     Class<?> homeHandlerClass = HomeHandler.class;      Method method = homeHandlerClass.getDeclaredMethod(             "handle", RoutingContext.class);     Lookup lookup = MethodHandles.lookup();     MethodHandle mh = lookup.unreflect(method);      MethodType factoryMethodType = MethodType.methodType(Handler.class);     MethodType functionMethodType = mh.type();     MethodHandle implementationMethodHandle = mh;      Handler<RoutingContext> lambda =             (Handler<RoutingContext>) LambdaMetafactory.metafactory(                     lookup,                     "handle",                     factoryMethodType,                      functionMethodType,                     implementationMethodHandle,                     implementationMethodHandle.type())              .getTarget()             .invokeExact();      lambda.handle(ctx);  } catch (Throwable e) {     e.printStackTrace(); } 

This gives the error:

java.lang.AbstractMethodError: Receiver class [...]$$Lambda$82/0x00000008001fa840 does not define or inherit an implementation of the resolved method abstract handle(Ljava/lang/Object;)V of interface io.vertx.core.Handler. 

I have tried a range of other options for functionMethodType and implementationMethodHandle, but have not managed to get this working yet. Also, even if I replace the RoutingContext.class reference with Object.class, this does not fix the error.

The only way I can get the lambda.handle(ctx) call to succeed is by changing HomeHandler so that it does not extend Handler, making HomeHandler::handle static, and changing RoutingContext.class to Object.class. Oddly I can still cast the resulting lambda to Handler<RoutingContext>, even though it no longer extends Handler.

My questions:

  1. How do I get LambdaMetaFactory to work with non-static methods?

  2. For this non-static SAM class HomeHandler, how does this work with instance allocation under the hood? Does LambdaMetaFactory create a single instance of the interface implementation, no matter how many method calls, since in this example there are no captured variables? Or does it create a new instance for each method call? Or was I supposed to create a single instance and pass it in to the API somehow?

  3. How do I get LambdaMetaFactory to work with generic methods?

 


Or was I supposed to create a single instance and pass it in to the API somehow?

Yes. HomeHandler::handle is an instance method, that means you need an instance to create a functional interface wrapper, or pass an instance every time you invoke it (for which Handler won't work as a FunctionalInterface type).

To use a captured instance you should:

  • Change factoryMethodType to also take a HomeHandler instance
  • Change functionMethodType to be the erased type of the SAM, which takes an Object as argument.
  • Change the instantiatedMethodType argument to be the type of the target method handle without the captured HomeHandler instance (since it's captured you don't need it again as a parameter).
  • Pass an instance of HomeHandler to invokeExact when creating the functional interface interface.

-

Class<?> homeHandlerClass = HomeHandler.class;  Method method = homeHandlerClass.getDeclaredMethod(         "handle", RoutingContext.class); Lookup lookup = MethodHandles.lookup(); MethodHandle mh = lookup.unreflect(method);  MethodType factoryMethodType = MethodType.methodType(Handler.class, HomeHandler.class); MethodType functionMethodType = MethodType.methodType(void.class, Object.class); MethodHandle implementationMethodHandle = mh;  Handler<RoutingContext> lambda =         (Handler<RoutingContext>) LambdaMetafactory.metafactory(                 lookup,                 "handle",                 factoryMethodType,                  functionMethodType,                 implementationMethodHandle,                 implementationMethodHandle.type().dropParameterTypes(0, 1))          .getTarget()         .invokeExact(new HomeHandler()); // capturing instance lambda.handle(ctx); 

Of course, since HomeHandler implements Handler, you could just use the captured instance directly;

new HomeHandler().handle(ctx); 

Or leverage the compiler to generate the metafactory code, which also uses invokedynamic, meaning that the CallSite returned by LambdaMetafactory.metafactory will only be created once:

Handler<RoutingContext> lambda = new HomeHandler()::handle; lambda.handle(ctx); 

Or, if the functional interface type is statically know:

MethodHandle theHandle = ... Object theInstance = ... MethodHandle adapted = theHandle.bindTo(theInstance); Handler<RoutingContext> lambda = ctxt -> {     try {         adapted.invokeExact(ctxt);     } catch (Throwable e) {         throw new RuntimeException(e);     } }; lambda.handle(new RoutingContext()); 

Comment

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: