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

baubqpgj  于 2023-05-27  发布在  Java
关注(0)|答案(2)|浏览(170)

我们有这个功能接口:

public interface Consumer<T> {

    void accept(T t);

}

我可以这样使用它:

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

我们如何在代码中解析lambda参数的实际泛型类型?
当我们将它用作内联实现时,从该类的方法中提取Integer并不困难。
我错过什么了吗?或者只是Java不支持lambda类?
更清楚地说:
lambda用MethodInvoker Package (在提到的handle中),在其execute(Message<?> message)中提取实际参数用于进一步的反射方法调用。在此之前,它使用Spring的ConversionService将提供的参数转换为目标参数。
在这种情况下,方法handle是在真实的应用工作之前的一些配置。
不同的问题,但对同一问题的解决方案的期望:Java: get actual type of generic method with lambda parameter

tcbh2hod

tcbh2hod1#

这是目前可以解决的,但只能以一种非常简单的方式解决,但让我首先解释几件事:
当你写一个lambda时,编译器插入一个指向LambdaMetafactory的动态调用指令和一个带有lambda主体的私有静态合成方法。常量池中的合成方法和方法句柄都包含泛型类型(如果lambda使用该类型或像您的示例中那样显式)。
现在,在运行时调用LambdaMetaFactory,并使用ASM生成一个类,该类实现函数接口和方法体,然后使用传递的任何参数调用私有静态方法。然后使用Unsafe.defineAnonymousClass(参见John Rose post)将其注入到原始类中,以便它可以访问私有成员等。
不幸的是,生成的类没有存储通用签名(它可以),因此您不能使用通常的反射方法来避免擦除
对于一个普通的类,你可以使用Class.getResource(ClassName + ".class")检查字节码,但是对于使用Unsafe定义的匿名类,你就不走运了。但是,您可以使用JVM参数使LambdaMetaFactory转储它们:

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

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

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

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

更新了Jonathan的建议

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

  • 它使用未记录的方法和类
  • 它极易受到JDK中代码更改的影响
  • 它不保留泛型类型,因此如果将List传入lambda,它将以List的形式输出
oaxa6hgo

oaxa6hgo2#

使用TypeRef Package lambda表达式。

@FunctionalInterface
interface TypeRef<T> {
    T create();

    default Class<?> getGenericType() {
        return create().getClass();
    }
}

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

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

输出:

class java.lang.String

相关问题