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:
How do I get
LambdaMetaFactoryto work with non-static methods?For this non-static SAM class
HomeHandler, how does this work with instance allocation under the hood? DoesLambdaMetaFactorycreate 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?How do I get
LambdaMetaFactoryto work with generic methods?
Edit: in addition to the great answers below, I came across this blog post explaining the mechanisms involved:
https://medium.freecodecamp.org/a-faster-alternative-to-java-reflection-db6b1e48c33e
Yes.
HomeHandler::handleis 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 whichHandlerwon't work as a FunctionalInterface type).To use a captured instance you should:
factoryMethodTypeto also take aHomeHandlerinstancefunctionMethodTypeto be the erased type of the SAM, which takes anObjectas argument.instantiatedMethodTypeargument to be the type of the target method handle without the capturedHomeHandlerinstance (since it's captured you don't need it again as a parameter).HomeHandlertoinvokeExactwhen creating the functional interface interface.-
Of course, since
HomeHandlerimplementsHandler, you could just use the captured instance directly;Or leverage the compiler to generate the metafactory code, which also uses
invokedynamic, meaning that theCallSitereturned byLambdaMetafactory.metafactorywill only be created once:Or, if the functional interface type is statically know: