具有泛型类型具体实现的 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/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
静态,然后更改RoutingContext.class
到 Object.class
。奇怪的是,我仍然可以将生成的 lambda 转换为 Handler<RoutingContext>
,即使它不再扩展 Handler
.
我的问题:
如何让 LambdaMetaFactory
使用非静态方法?
对于这个非静态 SAM class HomeHandler
,它如何与引擎盖下的实例分配一起工作? LambdaMetaFactory
是否创建接口实现的单个实例,无论有多少方法调用,因为在这个例子中没有捕获变量?或者它是否为每个方法调用创建一个新实例?还是我应该创建一个实例并将其以某种方式传递给 API?
如何让 LambdaMetaFactory
使用泛型方法?
编辑:除了下面的好答案之外,我还看到了这个博客 post 解释了所涉及的机制:
https://medium.freecodecamp.org/a-faster-alternative-to-java-reflection-db6b1e48c33e
Or was I supposed to create a single instance and pass it in to the API somehow?
是的。 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);
或者利用编译器生成 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()));
自 Java7
以来甚至存在此选项
我正在尝试使用 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/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
静态,然后更改RoutingContext.class
到 Object.class
。奇怪的是,我仍然可以将生成的 lambda 转换为 Handler<RoutingContext>
,即使它不再扩展 Handler
.
我的问题:
如何让
LambdaMetaFactory
使用非静态方法?对于这个非静态 SAM class
HomeHandler
,它如何与引擎盖下的实例分配一起工作?LambdaMetaFactory
是否创建接口实现的单个实例,无论有多少方法调用,因为在这个例子中没有捕获变量?或者它是否为每个方法调用创建一个新实例?还是我应该创建一个实例并将其以某种方式传递给 API?如何让
LambdaMetaFactory
使用泛型方法?
编辑:除了下面的好答案之外,我还看到了这个博客 post 解释了所涉及的机制:
https://medium.freecodecamp.org/a-faster-alternative-to-java-reflection-db6b1e48c33e
Or was I supposed to create a single instance and pass it in to the API somehow?
是的。 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);
或者利用编译器生成 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()));
自 Java7
以来甚至存在此选项