代码之家  ›  专栏  ›  技术社区  ›  Luke Hutchison

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

  •  6
  • Luke Hutchison  · 技术社区  · 7 年前

    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) {
            // ...
        }
    }
    

    这是我的尝试 羔羊工厂 :

    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.
    

    我已经尝试了一系列其他的选择 functionMethodType implementationMethodHandle RoutingContext.class Object.class ,这不会修复错误。

    只有这样我才能得到 lambda.handle(ctx) 成功的召唤是通过改变 HomeHandler 所以它不会延伸 Handler ,制作 HomeHandler::handle 静态和变化 Object.class类 . 奇怪的是,我仍然可以将结果lambda ,即使它不再延伸 .

    1. 我怎么才能得到 羔羊工厂 使用非静态方法?

    2. 对于这个非静态SAM类 家庭主妇 ,如何在引擎盖下进行实例分配?做 羔羊工厂 创建接口实现的单个实例,不管有多少个方法调用,因为在这个示例中没有捕获的变量?还是为每个方法调用创建一个新实例?或者我应该创建一个实例并以某种方式将其传递给API?

    3. 我怎么才能得到 羔羊工厂

    编辑:除了下面的答案之外,我还看到了一篇解释相关机制的博文:

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

    2 回复  |  直到 7 年前
        1
  •  7
  •   Jorn Vernee    7 年前

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

    HomeHandler::handle Handler 不会作为功能接口类型工作)。

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

    • 改变 factoryMethodType 也需要一个 HomeHandler 实例
    • functionMethodType 是被删除的SAM类型,需要 Object 作为论据。
    • 更改 instantiatedMethodType 参数是不包含捕获的
    • 传递实例 家庭主妇 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);
    

    工具 ,您可以直接使用捕获的实例;

    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());
    
        2
  •  5
  •   Holger    7 年前

    既然你说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 参数。此外,由于 T 在里面 Handler<T> Object ,您可以简单地使用 erase() 在该方法上键入以获取 samMethodType

    它并不总是那么简单。考虑绑定一个方法 static int method(int x) Consumer<Integer> . 然后 samMethodType方法 参数为 (Object)void ,的 参数为 (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()));
    

    这个选项甚至在Java7之后就存在了