Java:如何解析 lambda 参数的泛型类型?
Posted
技术标签:
【中文标题】Java:如何解析 lambda 参数的泛型类型?【英文标题】:Java: how to resolve generic type of lambda parameter? 【发布时间】:2014-07-14 20:22:39 【问题描述】:嗯,我们有FunctionalInterface
:
public interface Consumer<T>
void accept(T t);
我可以像这样使用它:
.handle(Integer p -> System.out.println(p * 2));
我们如何在代码中解析该 lambda 参数的实际 generic type
?
当我们将它用作内联实现时,从该类的方法中提取Integer
并不难。
我错过了什么吗?或者只是 java 不支持 lambda 类?
为了更干净:
该 lambda 用 MethodInvoker
包装(在提到的 handle
中),它在其 execute(Message<?> message)
中提取实际参数以用于进一步的反射方法调用。在此之前,它使用 Spring 的 ConversionService
将提供的参数转换为目标参数。
本例中的方法handle
是在真正的应用程序工作之前的一些配置器。
不同的问题,但对同一问题的解决方案的期望:Java: get actual type of generic method with lambda parameter
【问题讨论】:
不确定我是否理解;在您的示例中,您可以不使用Integer
,编译器会在此处进行类型推断。
没有。我真的需要知道那个类型:我们有一些converter
子系统可以将"2"
转换为int
(最简单的示例),如果它可以确定参数的类型。否则我们最终会得到java.lang.String cannot be cast to java.lang.Integer
。拥有该 Java 以某种方式理解实际类型。但是我怎样才能从代码中做到这一点?
再来一次:我想知道方法参数的实际泛型类型。我可以为课程做到这一点,但不能为 lambdas 做。就这样。不要试图理解我的应用程序的逻辑——它是 Spring,它有强大的Converter
子系统。例如。我可以编写和注册一些UserToPersonConverter
并将User
发送给我的消费者,它将被转换为Person
方法参数。它适用于内联类,但不适用于 lambdas
反射调用了什么?请使用所有这些相关信息编辑您的问题。
你可以在我的项目github.com/spring-projects/spring-integration-java-dsl中找到它。泛型解析是通过一个额外的Class<T>
参数完成的:github.com/spring-projects/spring-integration-java-dsl/blob/… 和一个内部解决方案LambdaMessageProcessor
为我们做这些事情。没有第三方,顺便说一句:只是当前状态下的 Java...
【参考方案1】:
我最近添加了对 TypeTools 解析 lambda 类型参数的支持。例如:
MapFunction<String, Integer> fn = str -> Integer.valueOf(str);
Class<?>[] typeArgs = TypeResolver.resolveRawArguments(MapFunction.class, fn.getClass());
解析的类型参数符合预期:
assert typeArgs[0] == String.class;
assert typeArgs[1] == Integer.class;
注意:底层实现使用 @danielbodart 概述的 ConstantPool 方法,已知该方法适用于 Oracle JDK 和 OpenJDK。
【讨论】:
按预期工作。非常感谢。现在我可以做到这一点:.<Integer>handle((p, h) -> p * 2)
并且可以使用我们的 conversion
系统将传入的 Object 转换为预期的 Integer
。【参考方案2】:
目前这是可以解决的,但只能以一种非常老套的方式解决,但让我先解释一下:
当您编写 lambda 时,编译器会插入一个指向 LambdaMetafactory 的动态调用指令和一个带有 lambda 主体的私有静态合成方法。常量池中的合成方法和方法句柄都包含泛型类型(如果 lambda 使用该类型或在您的示例中是显式的)。
现在在运行时调用 LambdaMetaFactory
并使用 ASM 生成一个类,该类实现功能接口,然后方法体调用私有静态方法并传递任何参数。然后使用Unsafe.defineAnonymousClass
(参见John Rose post)将其注入原始类,以便它可以访问私有成员等。
不幸的是,生成的 Class 不存储通用签名(它可以)所以你不能使用通常的反射方法来绕过擦除
对于普通类,您可以使用 Class.getResource(ClassName + ".class")
检查字节码,但对于使用 Unsafe
定义的匿名类,您就不走运了。但是,您可以使用 JVM 参数使 LambdaMetaFactory
转储它们:
java -Djdk.internal.lambda.dumpProxyClasses=/some/folder
通过查看转储的类文件(使用javap -p -s -v
),可以看到它确实调用了静态方法。但问题仍然是如何从 Java 本身中获取字节码。
不幸的是,这就是它的难点:
使用反射,我们可以调用Class.getConstantPool
,然后访问 MethodRefInfo 以获取类型描述符。然后我们可以使用 ASM 来解析它并返回参数类型。把它们放在一起:
Method getConstantPool = Class.class.getDeclaredMethod("getConstantPool");
getConstantPool.setAccessible(true);
ConstantPool constantPool = (ConstantPool) getConstantPool.invoke(lambda.getClass());
String[] methodRefInfo = constantPool.getMemberRefInfoAt(constantPool.size() - 2);
int argumentIndex = 0;
String argumentType = jdk.internal.org.objectweb.asm.Type.getArgumentTypes(methodRef[2])[argumentIndex].getClassName();
Class<?> type = (Class<?>) Class.forName(argumentType);
根据乔纳森的建议更新
现在理想情况下,LambdaMetaFactory
生成的类应该存储泛型类型签名(我可能会看看是否可以向 OpenJDK 提交补丁),但目前这是我们能做的最好的事情。上面的代码存在以下问题:
【讨论】:
我已经接受了这个答案,因为它足够聪明和棘手。无论如何,我们决定用额外的参数来解决这个问题 - 请参阅我的链接问题。谢谢 此示例适用于java.util.function.Function
,但不适用于其他一些功能接口,因为成员 ref 位于不同的索引处。对于 Oracle JDK 和 Open JDK,可以通过 constantPool.getMemberRefInfoAt(constantPool.size() - 2)
可靠地获取成员 ref
我遇到了一些其他不能正常工作的极端情况 - 涉及从某些方法引用解析的类型参数,以及从带有盒装整数的 lambdas/method refs 解析的类型参数。有关更新,请参阅 TypeTools。
@DanielWorthington-Bodart 关于“现在理想情况下 LambdaMetaFactory 生成的类应该存储泛型类型签名”,您是否在 OpenJDK 中跟进了这一点?我有一个问题,生成的合成方法不包含通用签名,通过查看 javap 输出进行验证。
泛型参数类型必须在运行时可解析的假设存在根本缺陷。以Function<String,String> f1 = Function.identity(); Function<Integer,Integer> f2 = Function.identity();
为例(identity()
返回的函数实现为 lambda 表达式,t -> t
)。在这方面,lambda 表达式与泛型类没有什么不同。因此,即使在这种 hack 有效的环境中,它也只能提供一部分功能。【参考方案3】:
使用 TypeRef
包装 lambda 表达式。
public interface Factory<T>
T create();
public abstract class TypeRef<T>
protected TypeRef(Factory<T> factory)
public Class<?> getGenericType()
return (Class<?>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0];
public class Main
public static void main(String[] args)
System.out.println(getFactoryTypeParameter(new TypeRef<>(() -> "hello")));
private static <T> Class<?> getFactoryTypeParameter(TypeRef<T> typeRef)
return typeRef.getGenericType();
输出:
class java.lang.String
【讨论】:
以上是关于Java:如何解析 lambda 参数的泛型类型?的主要内容,如果未能解决你的问题,请参考以下文章