具有泛型类型的具体实现的LambdaMetaFactory

时间:2018-10-14 12:25:05

标签: java lambda java-9 methodhandle lambda-metafactory

我正在尝试使用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设为静态,然后更改{{ 1}}至RoutingContext.class。奇怪的是,即使它不再扩展Object.class,我仍然可以将所得的lambda强制转换为Handler<RoutingContext>

我的问题:

  1. 如何让Handler使用非静态方法?

  2. 对于此非静态SAM类LambdaMetaFactory,它如何在后台进行实例分配? HomeHandler是否创建接口实现的单个实例,无论调用多少方法,因为在此示例中没有捕获的变量?还是为每个方法调用创建一个新实例?还是应该创建一个实例并将其以某种方式传递给API?

  3. 如何让LambdaMetaFactory与泛型方法一起使用?

编辑:除了下面的精彩回答,我还看到了这篇博客文章,解释了其中涉及的机制:

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

2 个答案:

答案 0 :(得分:7)

  

还是应该创建一个实例并以某种方式将其传递给API?

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

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

  • factoryMethodType更改为也包含一个HomeHandler实例
  • functionMethodType更改为SAM的擦除类型,它以Object作为参数。
  • instantiatedMethodType参数更改为没有捕获的HomeHandler实例的目标方法句柄的类型(由于已捕获,因此您不再需要它作为参数)。
  • 在创建功能接口界面时,将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);

或者,如果功能接口类型是静态已知的:

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

答案 1 :(得分:4)

由于您说过“ LambdaMetaFactory API太复杂了,真是可惜”,应该提到它可以做得更简单。

首先,在使用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());

您将使用绑定的接收方调用实例方法,目标方法(不包括接收方)的类型与instantiatedMethodType参数相同。此外,由于THandler<T>的边界是Object,因此您可以在该方法类型上简单地使用erase()来获取samMethodType参数的已删除签名

并不总是那么简单。考虑将方法static int method(int x)绑定到Consumer<Integer>。然后,samMethodType参数为(Object)voidinstantiatedMethodType参数为(Integer)void,而目标方法的签名为int(int)。您需要所有这些参数来正确描述要生成的代码。考虑到其他(前三个)参数通常都是由JVM填充的,因此该方法仅需要必需的最小值。

第二,如果您不需要最高性能,则可以简单地使用基于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()));

此选项自Java 7起就存在