简体   繁体   English

LambdaMetaFactory具有通用类型的具体实现

[英]LambdaMetaFactory with concrete implementation of generic type

I am trying to use Java's LambdaMetaFactory to dynamically implement a generic lambda, Handler<RoutingContext> : 我正在尝试使用Java的LambdaMetaFactory动态实现一个通用的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 : 这是我对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. 我已经为functionMethodTypeimplementationMethodHandle尝试了一系列其他选项,但还没有设法让它工作。 Also, even if I replace the RoutingContext.class reference with Object.class , this does not fix the error. 此外,即使我用Object.class替换RoutingContext.class引用,这也不能解决错误。

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 . 我能让lambda.handle(ctx)调用成功的唯一方法是改变HomeHandler ,使它不扩展Handler ,使HomeHandler::handle静态,并将RoutingContext.class更改为Object.class Oddly I can still cast the resulting lambda to Handler<RoutingContext> , even though it no longer extends Handler . 奇怪的是,我仍然可以将生成的lambda转换为Handler<RoutingContext> ,即使它不再扩展Handler

My questions: 我的问题:

  1. How do I get LambdaMetaFactory to work with non-static methods? 如何让LambdaMetaFactory与非静态方法一起使用?

  2. For this non-static SAM class HomeHandler , how does this work with instance allocation under the hood? 对于这个非静态SAM类HomeHandler ,它如何在实例分配下工作? 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? LambdaMetaFactory是否创建了接口实现的单个实例,无论有多少方法调用,因为在此示例中没有捕获的变量? 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? 或者我应该创建一个单独的实例并以某种方式将其传递给API?

  3. How do I get LambdaMetaFactory to work with generic methods? 如何让LambdaMetaFactory与泛型方法一起使用?

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 https://medium.freecodecamp.org/a-faster-alternative-to-java-reflection-db6b1e48c33e

Or was I supposed to create a single instance and pass it in to the API somehow? 或者我应该创建一个单独的实例并以某种方式将其传递给API?

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). HomeHandler::handle是一个实例方法,这意味着你需要一个实例来创建一个功能的接口包装器,或者每次调用它时都要传递一个实例( Handler不能作为FunctionalInterface类型工作)。

To use a captured instance you should: 要使用捕获的实例,您应该:

  • Change factoryMethodType to also take a HomeHandler instance 更改factoryMethodType以获取HomeHandler实例
  • Change functionMethodType to be the erased type of the SAM, which takes an Object as argument. functionMethodType更改为SAM的擦除类型,它将Object作为参数。
  • 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). instantiatedMethodType参数更改为没有捕获的HomeHandler实例的目标方法句柄的类型(因为它被捕获,您不再需要它作为参数)。
  • Pass an instance of HomeHandler to invokeExact when creating the functional interface interface. 在创建功能接口接口时,将invokeExact的实例HomeHandlerinvokeExact

- -

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; 当然,由于HomeHandler实现了Handler ,你可以直接使用捕获的实例;

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: 或利用编译器生成metafactory代码,该代码也使用invokedynamic ,这意味着LambdaMetafactory.metafactory返回的CallSite只会创建一次:

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. 既然你说“LambdaMetaFactory API太复杂了,那就太遗憾了”,应该提到它可以做得更简单。

First, when using LambdaMetaFactory , use it straight-forwardly: 首先,使用LambdaMetaFactory ,请直接使用它:

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. 您将使用绑定接收器调用实例方法,并且除了接收器之外的目标方法类型与instantiatedMethodType参数相同。 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. 此外,由于Handler<T>T的边界是Object ,因此您可以在该方法类型上使用erase()来获取samMethodType参数的已擦除签名。

It's not always that simple. 它并不总是那么简单。 Consider binding a method static int method(int x) to Consumer<Integer> . 考虑将方法static int method(int x)绑定到Consumer<Integer> Then, the samMethodType parameter is (Object)void , the instantiatedMethodType parameter is (Integer)void , whereas the target method's signature is int(int) . 然后, samMethodType参数是(Object)voidinstantiatedMethodType参数是(Integer)void ,而目标方法的签名是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. 考虑到其他(前三个)参数通常由JVM填充,这种方法确实只需要必要的最小值。

Second, if you don't need the maximum performance, you can simply use a Proxy based implementation: 其次,如果您不需要最高性能,则可以简单地使用基于Proxy的实现:

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 此选项甚至存在于Java 7之后

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM