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

Art*_*lan 16 java generics lambda

好吧,我们有FunctionalInterface:

public interface Consumer<T> {

    void accept(T t);

}
Run Code Online (Sandbox Code Playgroud)

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

.handle(Integer p -> System.out.println(p * 2));
Run Code Online (Sandbox Code Playgroud)

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

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

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

为了更清洁:

那个lambda被包裹着MethodInvoker(在提到中handle),它在其execute(Message<?> message)提取实际参数中进行进一步的反射方法调用.在此之前,它使用Spring将提供的参数转换为目标参数ConversionService.

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

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

Jon*_*han 12

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

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

已解析的类型args如预期:

assert typeArgs[0] == String.class;
assert typeArgs[1] == Integer.class;
Run Code Online (Sandbox Code Playgroud)

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


Dan*_*art 10

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

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

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

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

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

java -Djdk.internal.lambda.dumpProxyClasses=/some/folder
Run Code Online (Sandbox Code Playgroud)

通过查看转储的类文件(使用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);
Run Code Online (Sandbox Code Playgroud)

更新了乔纳森的建议

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

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

  • 我已经接受了这个答案,因为它足够聪明和棘手。无论如何,我们决定用额外的参数来解决这个问题 - 请参阅我的链接问题。谢谢 (2认同)