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

时间:2014-05-26 06:08:08

标签: java generics lambda

好吧,我们有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

3 个答案:

答案 0 :(得分:12)

我最近添加了对解析TypeTools的lambda类型参数的支持。例如:

MapFunction<String, Integer> fn = str -> Integer.valueOf(str);
Class<?>[] typeArgs = TypeResolver.resolveRawArguments(MapFunction.class, fn.getClass());

已解析的类型args符合预期:

assert typeArgs[0] == String.class;
assert typeArgs[1] == Integer.class;

注意:底层实现使用@danielbodart概述的ConstantPool方法,该方法已知可用于Oracle JDK和OpenJDK。

答案 1 :(得分:9)

这目前可以解决,但只是以一种漂亮的hackie方式,但我先解释一下:

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

现在在运行时调用LambdaMetaFactory并使用实现功能接口的ASM生成类,然后该方法的主体使用传递的任何参数调用私有静态方法。然后使用Unsafe.defineAnonymousClass将其注入原始类(请参阅John Rose post),以便它可以访问私有成员等。

不幸的是,生成的类没有存储通用签名(它可以),所以你不能使用通常的反射方法来解决擦除问题

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

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

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

不幸的是,它变成了hackie:

使用反射我们可以调用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);

根据Jonathan的建议更新

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

  • 它使用未记录的方法和类
  • 它非常容易受到JDK中的代码更改
  • 的影响
  • 它不保留泛型类型,因此如果您传递List&lt; String&gt;进入一个lambda它将作为List
  • 出现

答案 2 :(得分:1)

如果您的Lambdas 可序列化(SAM接口从java.io.Serializable扩展),那么此解决方案可以为您完成:

Reflection type inference on Java 8 Lambdas

如果您自己创建了SAM接口,则可能需要添加java.io.Serializable作为超级接口以使此方法有效。