Eu estou tentando usar Java de LambdaMetaFactory
implementar dinamicamente um 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) {
// ...
}
}
Aqui é minha tentativa em 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();
}
Isto dá o erro:
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.
Eu tentei uma série de outras opções para functionMethodType
e implementationMethodHandle
, mas não conseguiram obter esse trabalho ainda. Além disso, mesmo se eu substituir a RoutingContext.class
referência com Object.class
, isso não corrigir o erro.
A única maneira que eu posso obter a lambda.handle(ctx)
chamada para ter sucesso é mudar HomeHandler
para que ele não se estende Handler
, tornando HomeHandler::handle
estático, e mudando RoutingContext.class
para Object.class
. Estranhamente eu ainda pode lançar o lambda resultante Handler<RoutingContext>
, embora já não se estende Handler
.
Minhas perguntas:
Como faço para chegar
LambdaMetaFactory
ao trabalho com métodos não-estáticos?Para esta classe SAM não-estático
HomeHandler
, como isso funciona com a alocação instância sob o capô? Será queLambdaMetaFactory
criar uma única instância da implementação do interface, não importa quantas chamadas de método, uma vez que, neste exemplo, existem variáveis não capturado? Ou será que criar uma nova instância para cada chamada de método? Ou eu deveria criar uma única instância e passá-lo para a API de alguma forma?Como faço para chegar
LambdaMetaFactory
ao trabalho com métodos genéricos?
Edit: Além das grandes respostas abaixo, me deparei com este post explicando os mecanismos envolvidos:
https://medium.freecodecamp.org/a-faster-alternative-to-java-reflection-db6b1e48c33e
Ou eu deveria criar uma única instância e passá-lo para a API de alguma forma?
Sim. HomeHandler::handle
é um método de instância, isso significa que você precisa de uma instância para criar um wrapper interface funcional, ou passar uma instância cada vez que você chamá-lo (para o qual Handler
não irá funcionar como um tipo FunctionalInterface).
Para usar um exemplo capturado você deve:
- Mudança
factoryMethodType
para também dar umaHomeHandler
instância - Alterar
functionMethodType
a ser o tipo apagada do SAM, que leva umObject
como argumento. - Alterar o
instantiatedMethodType
argumento para ser o tipo da alça método de destino sem a capturouHomeHandler
instância (uma vez que é capturado, você não precisa-lo novamente como um parâmetro). - Passar uma instância de
HomeHandler
ainvokeExact
ao criar o interface de interface 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);
Claro, desde que HomeHandler
implementos Handler
, você pode simplesmente usar a instância capturados diretamente;
new HomeHandler().handle(ctx);
Ou alavancar o compilador para gerar o código metafactory, que também usa invokedynamic
, o que significa que o CallSite
retornado por LambdaMetafactory.metafactory
só será criado uma vez que:
Handler<RoutingContext> lambda = new HomeHandler()::handle;
lambda.handle(ctx);
Ou, se o tipo de interface funcional é estaticamente 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());