Estoy tratando de utilizar Java LambdaMetaFactory
para implementar dinámicamente una lambda genérico, 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) {
// ...
}
}
Aquí está mi intento 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();
}
Esto da el 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.
He probado una variedad de otras opciones para functionMethodType
y implementationMethodHandle
, pero no han logrado conseguir este trabajo todavía. Además, incluso si se sustituye la RoutingContext.class
referencia con Object.class
, esto no soluciona el error.
La única manera que puedo conseguir la lambda.handle(ctx)
llamada se realice correctamente es cambiando HomeHandler
de modo que no se extiende Handler
, por lo HomeHandler::handle
estático, y el cambio RoutingContext.class
a Object.class
. Por extraño que todavía puedo emitir el lambda resultante Handler<RoutingContext>
, a pesar de que ya no se extiende Handler
.
Mis preguntas:
¿Cómo llego
LambdaMetaFactory
a trabajar con métodos no estáticos?Para esta clase SAM no estático
HomeHandler
, ¿cómo funciona esto con la asignación de instancia bajo el capó? NoLambdaMetaFactory
crear una sola instancia de la implementación de la interfaz, independientemente del número de llamadas a métodos, ya que en este ejemplo hay variables no capturaron? ¿O crea una nueva instancia para cada llamada al método? O se supone que iba a crear una instancia única y pasarlo a la API de alguna manera?¿Cómo llego
LambdaMetaFactory
a trabajar con métodos genéricos?
Editar: además de las grandes respuestas a continuación, me encontré con este post que explica los mecanismos que intervienen:
https://medium.freecodecamp.org/a-faster-alternative-to-java-reflection-db6b1e48c33e
O se supone que iba a crear una instancia única y pasarlo a la API de alguna manera?
Si. HomeHandler::handle
es un método de instancia, lo que significa que necesita una instancia para crear un envoltorio interfaz funcional, o pasar una instancia cada vez que se invoca (para el que Handler
no va a funcionar como un tipo FunctionalInterface).
Para usar una instancia capturado debe:
- Cambio
factoryMethodType
tomar también unaHomeHandler
instancia - El cambio
functionMethodType
sea el tipo de borrado de la SAM, que tiene unaObject
como argumento. - Cambiar el
instantiatedMethodType
argumento de que es el tipo de la manija método de destino sin el capturadoHomeHandler
instancia (ya que es capturado usted no necesita otra vez como un parámetro). - Pasar una instancia de
HomeHandler
ainvokeExact
la hora de crear la interfaz interfaz funcional.
-
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);
Por supuesto, ya que HomeHandler
los implementos Handler
, sólo podría utilizar la instancia capturado directamente;
new HomeHandler().handle(ctx);
O aprovechar el compilador para generar el código metafactory, que también utiliza invokedynamic
, lo que significa que el CallSite
devuelto por LambdaMetafactory.metafactory
solamente se creará una vez:
Handler<RoutingContext> lambda = new HomeHandler()::handle;
lambda.handle(ctx);
O bien, si el tipo de interfaz funcional es estáticamente saber:
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());