【问题标题】:LambdaMetaFactory with concrete implementation of generic type具有泛型类型的具体实现的 LambdaMetaFactory
【发布时间】:2019-03-19 01:08:44
【问题描述】:

我正在尝试使用 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。奇怪的是,我仍然可以将生成的 lambda 转换为 Handler&lt;RoutingContext&gt;,即使它不再扩展 Handler

我的问题:

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

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

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

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

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

【问题讨论】:

  • @Holger 你好像回答了很多相关的问题,你大概知道答案了:-)
  • 很遗憾,@Username 不适用于尚未涉及目标用户的帖子。

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


【解决方案1】:

或者我应该创建一个实例并以某种方式将其传递给 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,这意味着LambdaMetafactory.metafactory返回的CallSite只会被创建一次:

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

【讨论】:

  • 感谢 Jorn 的彻底而清晰的回复。是的,如果这只是一个类,我可以使用你最后几个建议中的一个,但我计划使用 LambdaMetaFactory 为类路径或模块路径中用 @ 注释的所有类动态注册 Vert.x 路由处理程序987654341@ 注释,也实现了Handler&lt;RoutingContext&gt;,通过使用 ClassGraph 库扫描类路径。我想我可以只实例化每个匹配的类,全部并将实例转换为Handler&lt;RoutingContext&gt;——也许我的目光越界了。
  • 很遗憾LambdaMetaFactory API 如此复杂——我知道它是为编译器和序列化库编写者设计的,但它也有很多通用用例。
  • @LukeHutchison 我添加了另一个选项,我认为您应该可以使用。它看起来有点尴尬,需要注意的是你需要知道 FI 类型,因为你需要它作为 lambda 表达式的目标。但是从 MethodHandle 到 FI 包装器应该比 LambdaMetafactory 更容易使用。
  • 谢谢,这是一个非常有趣的混合选项。这让我觉得它是一个“静态代理”,类似于 InvocationHandler 作为“动态代理”的工作方式。
  • @LukeHutchison 谈到基于InvocationHandler 的代理,这样的解决方案确实已经存在。它允许几乎以单行方式实现接口。我添加了一个答案来补充这个答案。
【解决方案2】:

既然你说“可惜 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&lt;T&gt; 中的边界是Object,您可以简单地在该方法类型上使用erase() 来获取samMethodType 参数的已擦除签名。

事情并不总是那么简单。考虑将方法static int method(int x) 绑定到Consumer&lt;Integer&gt;。那么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 开始就存在

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-09-20
    • 1970-01-01
    • 1970-01-01
    • 2011-06-11
    • 2013-11-22
    相关资源
    最近更新 更多