目前可以解决这个问题,但只能以一种非常黑客的方式解决,但让我先解释一些事情:
当您编写 lambda 时,编译器会插入指向 LambdaMetafactory 的动态调用指令和一个包含 lambda 主体的私有静态合成方法。常量池中的合成方法和方法句柄都包含泛型类型(如果 lambda 使用该类型或如示例所示是显式的)。
现在,在运行时调用 ,并使用 ASM 生成一个类,该类实现函数接口,然后使用传递的任何参数调用方法的函数接口和方法主体。然后使用它注入原始类(参见John Rose post),以便它可以访问私有成员等。LambdaMetaFactory
Unsafe.defineAnonymousClass
不幸的是,生成的类不存储通用签名(它可以),因此您无法使用通常的反射方法来绕过擦除
对于普通类,您可以使用检查字节码,但对于使用定义的匿名类,您运气不好。但是,您可以使用 JVM 参数将它们转储出来:Class.getResource(ClassName + ".class")
Unsafe
LambdaMetaFactory
java -Djdk.internal.lambda.dumpProxyClasses=/some/folder
通过查看转储的类文件(使用 ),可以看到它确实调用了静态方法。但问题仍然是如何从Java本身中获取字节码。javap -p -s -v
不幸的是,这是它变得黑客的地方:
使用反射,我们可以调用然后访问 MethodRefInfo 以获取类型描述符。然后,我们可以使用 ASM 来解析此参数并返回参数类型。把它们放在一起:Class.getConstantPool
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);
更新了乔纳森的建议
现在理想情况下,生成的类应该存储泛型类型签名(我可能会看看是否可以向OpenJDK提交补丁),但目前这是我们能做的最好的。上面的代码存在以下问题:LambdaMetaFactory
- 它使用未记录的方法和类
- 它极易受到JDK中代码更改的影响。
- 它不会保留泛型类型,因此,如果您将List<String>传递到lambda中,它将作为List出来