我正在尝试使用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) {
        // ...
    }
}

这是我对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();
}

这给出了错误:
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.

我已经尝试了functionMethodTypeimplementationMethodHandle的一系列其他选项,但尚未设法使它起作用。同样,即使我将RoutingContext.class引用替换为Object.class,也无法解决该错误。

我可以使lambda.handle(ctx)调用成功的唯一方法是更改​​HomeHandler,使其不扩展Handler,将HomeHandler::handle设置为静态,然后将RoutingContext.class更改为Object.class。奇怪的是,即使它不再扩展Handler<RoutingContext>,我仍然可以将生成的lambda转换为Handler

我的问题:
  • 如何获取LambdaMetaFactory以使用非静态方法?
  • 对于此非静态SAM类HomeHandler,它如何在后台进行实例分配?不管有多少个方法调用,LambdaMetaFactory是否创建接口(interface)实现的单个实例,因为在此示例中没有捕获的变量?还是为每个方法调用创建一个新实例?还是应该创建一个实例并以某种方式将其传递给API?
  • 如何获得LambdaMetaFactory与通用方法一起使用?

  • 编辑:除了以下出色的答案外,我还浏览了此博客文章,解释了其中涉及的机制:

    https://medium.freecodecamp.org/a-faster-alternative-to-java-reflection-db6b1e48c33e

    最佳答案



    是的。 HomeHandler::handle是一个实例方法,这意味着您需要一个实例来创建功能性接口(interface)包装器,或在每次调用它时传递一个实例(对于它,Handler不能用作FunctionalInterface类型)。

    要使用捕获的实例,您应该:

  • 更改factoryMethodType以也接受一个HomeHandler实例
  • functionMethodType更改为SAM的已擦除类型,该方法将Object作为参数。
  • instantiatedMethodType参数更改为没有捕获的HomeHandler实例的目标方法句柄的类型(由于已捕获,因此不再需要它作为参数)。
  • 创建功能接口(interface)界面时,将HomeHandler的实例传递给invokeExact

  • --
    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);
    

    当然,由于HomeHandler实现了Handler,您可以直接使用捕获的实例。
    new HomeHandler().handle(ctx);
    

    或利用编译器生成元工厂代码,该代码也使用invokedynamic,这意味着CallSite返回的LambdaMetafactory.metafactory将仅创建一次:
    Handler<RoutingContext> lambda = new HomeHandler()::handle;
    lambda.handle(ctx);
    

    或者,如果功能接口(interface)类型是静态已知的:
    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());
    

    10-07 15:21