简体   繁体   English

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

[英]Java: how to resolve generic type of lambda parameter?

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 type of that lambda parameter in our code?我们如何在代码中解析 lambda 参数的实际generic type

When we use it as an inline implementation it isn't so difficult to extract the Integer from the method of that class.当我们将它用作内联实现时,从 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. lambda 用MethodInvoker包装(在提到的handle中),它在其execute(Message<?> message)中提取实际参数以进一步调用反射方法。 Before that it converts provided arguments to target params using Spring's ConversionService .在此之前,它使用 Spring 的ConversionService将提供的 arguments 转换为目标参数。

The method handle in 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

I recently added support for resolving lambda type arguments to TypeTools . 我最近添加了对解决TypeTools的 lambda类型参数的支持 Ex: 例如:

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

The resolved type args are as expected: 已解析的类型args如预期:

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。

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 LambdaMetafactory and a private static synthetic method with the body of the lambda. 编写lambda时,编译器会插入一个指向LambdaMetafactory的动态调用指令和一个带有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使用类型或在示例中是显式的)。

Now at runtime the LambdaMetaFactory is 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. 现在在运行时调用LambdaMetaFactory并使用实现功能接口的ASM生成类,然后该方法的主体使用传递的任何参数调用私有静态方法。 It is then injected into the original class using Unsafe.defineAnonymousClass (see John Rose post ) so it can access the private members etc. 然后使用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 Unsafe you are out of luck. 对于普通类,您可以使用Class.getResource(ClassName + ".class") .class Class.getResource(ClassName + ".class")检查字节码,但对于使用Unsafe定义的匿名类,您运气不好。 However you can make the LambdaMetaFactory dump them out with the JVM argument: 但是,您可以使用JVM参数将LambdaMetaFactory转储出去:

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. 通过查看转储的类文件(使用javap -p -s -v ),可以看出它确实调用了静态方法。 But the problem remains how to get the bytecode from within Java itself. 但问题仍然是如何从Java本身获取字节码。

This unfortunately is where it gets hackie: 不幸的是,这是hackie的地方:

Using reflection we can call Class.getConstantPool and then access the MethodRefInfo to get the type descriptors. 使用反射我们可以调用Class.getConstantPool然后访问MethodRefInfo来获取类型描述符。 We can then use ASM to parse this and return the argument types. 然后我们可以使用ASM来解析它并返回参数类型。 Putting it all together: 把它们放在一起:

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 LambdaMetaFactory should 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. 现在理想情况下, LambdaMetaFactory生成的类应该存储泛型类型签名(我可能会看到我是否可以向OpenJDK提交补丁)但是目前这是我们能做的最好的。 The code above has the following problems: 上面的代码有以下问题:

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

If your Lambdas are serializable (the SAM interfaces extends from java.io.Serializable ), then this solution could do it for you: 如果你的Lambdas是可序列化的 (SAM接口从java.io.Serializable扩展),那么这个解决方案可以为你做到:

Reflection type inference on Java 8 Lambdas Java 8 Lambdas上的反射类型推断

If you created the SAM interfaces yourself, it may be worth adding java.io.Serializable as a superinterface to make this method work. 如果您自己创建了SAM接口,则可能需要添加java.io.Serializable作为超级接口以使此方法有效。

Use a TypeRef to wrap the lambda expression.使用TypeRef来包装 lambda 表达式。

public interface Factory<T> {
    T create();
}

public abstract class TypeRef<T> {
    protected TypeRef(Factory<T> factory) {}

    public Class<?> getGenericType() {
        return (Class<?>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0];
    }
}

public class Main {
    public static void main(String[] args) {
        System.out.println(getFactoryTypeParameter(new TypeRef<>(() -> "hello"){}));
    }

    private static <T> Class<?> getFactoryTypeParameter(TypeRef<T> typeRef) {
        return typeRef.getGenericType();
    }
}

output: output:

class java.lang.String

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM