[英]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.
我已經為functionMethodType
和implementationMethodHandle
嘗試了一系列其他選項,但還沒有設法讓它工作。 此外,即使我用Object.class
替換RoutingContext.class
引用,這也不能解決錯誤。
我能讓lambda.handle(ctx)
調用成功的唯一方法是改變HomeHandler
,使它不擴展Handler
,使HomeHandler::handle
靜態,並將RoutingContext.class
更改為Object.class
。 奇怪的是,我仍然可以將生成的lambda轉換為Handler<RoutingContext>
,即使它不再擴展Handler
。
我的問題:
如何讓LambdaMetaFactory
與非靜態方法一起使用?
對於這個非靜態SAM類HomeHandler
,它如何在實例分配下工作? LambdaMetaFactory
是否創建了接口實現的單個實例,無論有多少方法調用,因為在此示例中沒有捕獲的變量? 或者它是否為每個方法調用創建一個新實例? 或者我應該創建一個單獨的實例並以某種方式將其傳遞給API?
如何讓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
的實例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);
或利用編譯器生成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)void
, instantiatedMethodType
參數是(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.