簡體   English   中英

LambdaMetaFactory具有通用類型的具體實現

[英]LambdaMetaFactory with concrete implementation of generic type

我正在嘗試使用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嘗試了一系列其他選項,但還沒有設法讓它工作。 此外,即使我用Object.class替換RoutingContext.class引用,這也不能解決錯誤。

我能讓lambda.handle(ctx)調用成功的唯一方法是改變HomeHandler ,使它不擴展Handler ,使HomeHandler::handle靜態,並將RoutingContext.class更改為Object.class 奇怪的是,我仍然可以將生成的lambda轉換為Handler<RoutingContext> ,即使它不再擴展Handler

我的問題:

  1. 如何讓LambdaMetaFactory與非靜態方法一起使用?

  2. 對於這個非靜態SAM類HomeHandler ,它如何在實例分配下工作? LambdaMetaFactory是否創建了接口實現的單個實例,無論有多少方法調用,因為在此示例中沒有捕獲的變量? 或者它是否為每個方法調用創建一個新實例? 或者我應該創建一個單獨的實例並以某種方式將其傳遞給API?

  3. 如何讓LambdaMetaFactory與泛型方法一起使用?

編輯:除了下面的好答案,我還看到了這篇博文,解釋了所涉及的機制:

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

或者我應該創建一個單獨的實例並以某種方式將其傳遞給API?

是。 HomeHandler::handle是一個實例方法,這意味着你需要一個實例來創建一個功能的接口包裝器,或者每次調用它時都要傳遞一個實例( Handler不能作為FunctionalInterface類型工作)。

要使用捕獲的實例,您應該:

  • 更改factoryMethodType以獲取HomeHandler實例
  • functionMethodType更改為SAM的擦除類型,它將Object作為參數。
  • instantiatedMethodType參數更改為沒有捕獲的HomeHandler實例的目標方法句柄的類型(因為它被捕獲,您不再需要它作為參數)。
  • 在創建功能接口接口時,將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);

當然,由於HomeHandler實現了Handler ,你可以直接使用捕獲的實例;

new HomeHandler().handle(ctx);

或利用編譯器生成metafactory代碼,該代碼也使用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());

既然你說“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參數相同。 此外,由於Handler<T>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之后

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM