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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/23863716/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-14 08:40:24  来源:igfitidea点击:

Java: how to resolve generic type of lambda parameter?

javagenericslambda

提问by Artem Bilan

Well, we have FunctionalInterface:

好吧,我们有FunctionalInterface

public interface Consumer<T> {

    void accept(T t);

}

And I can use it like:

我可以像这样使用它:

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

How can we resolve the actual generic typeof that lambda parameter in our code?

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

When we use it as an inline implementation it isn't so difficult to extract the Integerfrom the method of that class.

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

Do I miss anything? Or just java doesn't support it for lambda classes ?

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

To be more cleaner:

为了更干净:

That lambda is wrapped with MethodInvoker(in the mentioned handle), which in its execute(Message<?> message)extracts actual parameters for further reflection method invocation. Before that it converts provided arguments to target params using Spring's ConversionService.

该 lambda 用MethodInvoker(在提到的handle)中包装,它在其中execute(Message<?> message)提取实际参数以用于进一步的反射方法调用。在此之前,它使用 Spring 的ConversionService.

The method handlein this case is some configurer before the real application work.

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

The different question, but with expectation for the solution for the same issue: Java: get actual type of generic method with lambda parameter

不同的问题,但期望针对同一问题的解决方案:Java: get actual type of generic method with lambda parameter

采纳答案by Jonathan

I recently added support for resolving lambda type arguments to TypeTools. Ex:

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

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

The resolved type args are as expected:

解析的类型参数符合预期:

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

Note: The underlying implementation uses the ConstantPool approach outlined by @danielbodart which is known to work on Oracle JDK and OpenJDK.

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

回答by Daniel Worthington-Bodart

This is currently possible to solve but only in a pretty hackie way, but let me first explain a few things:

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

When you write a lambda, the compiler inserts a dynamic invoke instruction pointing to the LambdaMetafactoryand a private static synthetic method with the body of the lambda. The synthetic method and the method handle in the constant pool both contain the generic type (if the lambda uses the type or is explicit as in your examples).

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

Now at runtime the LambdaMetaFactoryis called and a class is generated using ASM that implements the functional interface and the body of the method then calls the private static method with any arguments passed. It is then injected into the original class using Unsafe.defineAnonymousClass(see John Rose post) so it can access the private members etc.

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

Unfortunately the generated Class does not store the generic signatures (it could) so you can't use the usual reflection methods that allow you to get around erasure

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

For a normal Class you could inspect the bytecode using Class.getResource(ClassName + ".class")but for anonymous classes defined using Unsafeyou are out of luck. However you can make the LambdaMetaFactorydump them out with the JVM argument:

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

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

By looking at the dumped class file (using javap -p -s -v), one can see that it does indeed call the static method. But the problem remains how to get the bytecode from within Java itself.

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

This unfortunately is where it gets hackie:

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

Using reflection we can call Class.getConstantPooland then access the MethodRefInfo to get the type descriptors. We can then use ASM to parse this and return the argument types. Putting it all together:

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

UPDATED with Jonathan's suggestion

更新了乔纳森的建议

Now ideally the classes generated by LambdaMetaFactoryshould store the generic type signatures (I might see if I can submit a patch to the OpenJDK) but currently this is the best we can do. The code above has the following problems:

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

  • It uses undocumented methods and classes
  • It is extremely vulnerable to code changes in the JDK
  • It doesn't preserve the generic types, so if you pass List<String> into a lambda it will come out as List
  • 它使用未记录的方法和类
  • 它极易受到 JDK 中代码更改的影响
  • 它不保留泛型类型,因此如果您将 List<String> 传递到 lambda 中,它将作为 List 出现