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&lt;?&gt; 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&lt;T&gt; 参数完成的: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。

【讨论】:

按预期工作。非常感谢。现在我可以做到这一点:.&lt;Integer&gt;handle((p, h) -&gt; 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 提交补丁),但目前这是我们能做的最好的事情。上面的代码存在以下问题:

它使用未记录的方法和类 它极易受到 JDK 中代码更改的影响 它不保留泛型类型,因此如果将 List 传递给 lambda,它将以 List 的形式出现

【讨论】:

我已经接受了这个答案,因为它足够聪明和棘手。无论如何,我们决定用额外的参数来解决这个问题 - 请参阅我的链接问题。谢谢 此示例适用于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&lt;String,String&gt; f1 = Function.identity(); Function&lt;Integer,Integer&gt; f2 = Function.identity(); 为例(identity() 返回的函数实现为 lambda 表达式,t -&gt; 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 参数的泛型类型?的主要内容,如果未能解决你的问题,请参考以下文章

如何在运行时获取泛型的类型

JAVA中的泛型类是啥东西?

如何获取java泛型的参数类型

java中的泛型

Java中的泛型

怎么取出类的泛型类型