Java:如何解析lambda参数的泛型类型?

2022-09-02 10:18:30

好吧,我们有:FunctionalInterface

public interface Consumer<T> {

    void accept(T t);

}

我可以像这样使用它:

.handle(Integer p -> System.out.println(p * 2));

我们如何在代码中解析该 lambda 参数的实际值?generic type

当我们将其用作内联实现时,从该类的方法中提取并不困难。Integer

我错过了什么吗?或者只是java不支持lambda类?

为了更干净:

该 lambda 被包装(在上述内容中),它在其中提取实际参数以进行进一步的反射方法调用。在此之前,它使用Spring的将提供的参数转换为目标参数。MethodInvokerhandleexecute(Message<?> message)ConversionService

在这种情况下,方法是在实际应用程序工作之前的一些配置器。handle

不同的问题,但期望解决同一问题:Java:使用lambda参数获取实际类型的泛型方法


答案 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。


答案 2

目前可以解决这个问题,但只能以一种非常黑客的方式解决,但让我先解释一些事情:

当您编写 lambda 时,编译器会插入指向 LambdaMetafactory 的动态调用指令和一个包含 lambda 主体的私有静态合成方法。常量池中的合成方法和方法句柄都包含泛型类型(如果 lambda 使用该类型或如示例所示是显式的)。

现在,在运行时调用 ,并使用 ASM 生成一个类,该类实现函数接口,然后使用传递的任何参数调用方法的函数接口和方法主体。然后使用它注入原始类(参见John Rose post),以便它可以访问私有成员等。LambdaMetaFactoryUnsafe.defineAnonymousClass

不幸的是,生成的类不存储通用签名(它可以),因此您无法使用通常的反射方法来绕过擦除

对于普通类,您可以使用检查字节码,但对于使用定义的匿名类,您运气不好。但是,您可以使用 JVM 参数将它们转储出来:Class.getResource(ClassName + ".class")UnsafeLambdaMetaFactory

java -Djdk.internal.lambda.dumpProxyClasses=/some/folder

通过查看转储的类文件(使用 ),可以看到它确实调用了静态方法。但问题仍然是如何从Java本身中获取字节码。javap -p -s -v

不幸的是,这是它变得黑客的地方:

使用反射,我们可以调用然后访问 MethodRefInfo 以获取类型描述符。然后,我们可以使用 ASM 来解析此参数并返回参数类型。把它们放在一起:Class.getConstantPool

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);

更新了乔纳森的建议

现在理想情况下,生成的类应该存储泛型类型签名(我可能会看看是否可以向OpenJDK提交补丁),但目前这是我们能做的最好的。上面的代码存在以下问题:LambdaMetaFactory

  • 它使用未记录的方法和类
  • 它极易受到JDK中代码更改的影响。
  • 它不会保留泛型类型,因此,如果您将List<String>传递到lambda中,它将作为List出来

推荐