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
作为超级接口以使此方法有效。