LambdaMetaFactory with concrete implementation of generic type
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 aHomeHandler
instance - Change
functionMethodType
to be the erased type of the SAM, which takes anObject
as argument. - Change the
instantiatedMethodType
argument to be the type of the target method handle without the capturedHomeHandler
instance (since it's captured you don't need it again as a parameter). - Pass an instance of
HomeHandler
toinvokeExact
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());
Since you said “it's a shame the LambdaMetaFactory API is so complex”, it should be mentioned it can be done simpler.
First, when using LambdaMetaFactory
, use it straight-forwardly:
Lookup lookup = MethodHandles.lookup();
MethodType fType = MethodType.methodType(void.class, RoutingContext.class);
MethodHandle mh = lookup.findVirtual(HomeHandler.class, "handle", fType);
Handler<RoutingContext> lambda = (Handler<RoutingContext>) LambdaMetafactory.metafactory(
lookup, "handle", MethodType.methodType(Handler.class, HomeHandler.class),
fType.erase(), mh, fType).getTarget().invokeExact(new HomeHandler());
You are going to invoke an instance method with a bound receiver and the target method’s type excluding the receiver is identical to the instantiatedMethodType
parameter. Further, since the bound of T
in Handler<T>
is Object
, you can simply use erase()
on that method type to get the erased signature for the samMethodType
parameter.
It’s not always that simple. Consider binding a method static int method(int x)
to Consumer<Integer>
. Then, the samMethodType
parameter is (Object)void
, the instantiatedMethodType
parameter is (Integer)void
, whereas the target method’s signature is int(int)
. You need all these parameters to correctly describe the code to generate. Considering that the other (first three) parameters are normally filled in by the JVM anyway, this method does already require only the necessary minimum.
Second, if you don’t need the maximum performance, you can simply use a Proxy
based implementation:
MethodHandle mh = MethodHandles.lookup().findVirtual(HomeHandler.class,
"handle", MethodType.methodType(void.class, RoutingContext.class));
Handler<RoutingContext> lambda = MethodHandleProxies.asInterfaceInstance(
Handler.class, mh.bindTo(new HomeHandler()));
This option even exists since Java 7