Java:如何解析genericslambda参数?

好吧,我们有FunctionalInterface

 public interface Consumer { void accept(T t); } 

我可以像以下一样使用它:

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

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

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

我想念什么吗? 或者只是java不支持lambda类?

为了更清洁:

该lambda用MethodInvoker (在上面提到的handle )包装,在其execute(Message message)提取实际参数以进行进一步的reflection方法调用。 在此之前,它使用Spring的ConversionService将提供的参数转换为目标参数。

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

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

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

 MapFunction 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。

这目前可以解决,但只是以一种非常讨厌的方式,但我先解释一下:

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

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

不幸的是,生成的类不存储通用签名(它可能),所以你不能使用通常的reflection方法,让你绕过擦除

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

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

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

不幸的是,这是hackie的地方:

使用reflection我们可以调用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生成的类应该存储generics类型签名(我可能会看到我是否可以向OpenJDK提交补丁)但是目前这是我们能做的最好的。 上面的代码有以下问题:

  • 它使用未记录的方法和类
  • 它非常容易受到JDK中代码更改的影响
  • 它不保留generics类型,因此如果将List 传递给lambda,它将作为List出现

如果你的Lambdas是可序列化的 (SAM接口从java.io.Serializable扩展),那么这个解决方案可以为你做到:

Java 8 Lambdas上的reflection类型推断

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