Java 8 Lambdas上的reflectiontypes推断

我正在尝试Java 8中的新Lambdas,并且正在寻找一种在lambda类上使用reflection来获取lambda函数的返回types的方法。 我特别感兴趣的是lambda实现通用超接口的情况。 在下面的代码示例中, MapFunction<F, T>是通用超接口,我正在寻找一种方法来查找绑定到通用参数T

虽然Java在编译器之后丢弃了大量的genericstypes信息,但generics超类和generics超接口的子类(和匿名子类)却保留了这种types的信息。 通过reflection,这些types是可访问的。 在下面的例子(例1)中 ,reflection告诉我, MyMapper实现将java.lang.Integer绑定到genericstypes参数T

即使对于本身是generics的子类,也有一些方法可以找出与generics参数绑定的是什么,如果其他的已知的话。 在下面的例子中考虑情况2 ,其中FT绑定到相同types的IdentityMapper 。 当我们知道的时候,如果我们知道参数typesT (在我的情况下,我们知道的话),我们知道typesF

现在的问题是,我怎样才能实现Java 8 lambdas类似的东西呢? 由于它们实际上不是generics超级界面的常规子类,因此上述方法不起作用。 具体来说,我可以找出parseLambdajava.lang.Integer绑定到TidentityLambda parseLambda绑定到FT

PS:理论上讲,应该可以反编译lambda代码,然后使用embedded式编译器(如JDT)并进入其types推断。 我希望有一个更简单的方法来做到这一点;-)

 /** * The superinterface. */ public interface MapFunction<F, T> { T map(F value); } /** * Case 1: A non-generic subclass. */ public class MyMapper implements MapFunction<String, Integer> { public Integer map(String value) { return Integer.valueOf(value); } } /** * A generic subclass */ public class IdentityMapper<E> implements MapFunction<E, E> { public E map(E value) { return value; } } /** * Instantiation through lambda */ public MapFunction<String, Integer> parseLambda = (String str) -> { return Integer.valueOf(str); } public MapFunction<E, E> identityLambda = (value) -> { return value; } public static void main(String[] args) { // case 1 getReturnType(MyMapper.class); // -> returns java.lang.Integer // case 2 getReturnTypeRelativeToParameter(IdentityMapper.class, String.class); // -> returns java.lang.String } private static Class<?> getReturnType(Class<?> implementingClass) { Type superType = implementingClass.getGenericInterfaces()[0]; if (superType instanceof ParameterizedType) { ParameterizedType parameterizedType = (ParameterizedType) superType; return (Class<?>) parameterizedType.getActualTypeArguments()[1]; } else return null; } private static Class<?> getReturnTypeRelativeToParameter(Class<?> implementingClass, Class<?> parameterType) { Type superType = implementingClass.getGenericInterfaces()[0]; if (superType instanceof ParameterizedType) { ParameterizedType parameterizedType = (ParameterizedType) superType; TypeVariable<?> inputType = (TypeVariable<?>) parameterizedType.getActualTypeArguments()[0]; TypeVariable<?> returnType = (TypeVariable<?>) parameterizedType.getActualTypeArguments()[1]; if (inputType.getName().equals(returnType.getName())) { return parameterType; } else { // some logic that figures out composed return types } } return null; } 

如何将lambda代码映射到接口实现的确切决定由实际运行时环境决定。 原则上,实现相同原始接口的所有lambda可以像MethodHandleProxies一样共享一个运行时类。 为特定的lambdas使用不同的类是由实际的LambdaMetafactory实现执行的优化 ,但不是旨在帮助debugging或reflection的function。

因此,即使在lambda接口实现的实际运行时类中find更多详细信息,它也将成为当前使用的运行时环境的工件,可能在您的当前环境的不同实现或其他版本中不可用。

如果lambda是Serializable ,则可以使用序列化表单包含实例化接口types的方法签名的事实,以将实际的typesvariables值拼凑在一起。

这目前是可以解决的,但是只能用一种很好的方式解决,但是让我先解释一些事情:

当你编写一个lambda时,编译器插入一个指向LambdaMetafactory的dynamic调用指令和一个包含lambda体的私有静态合成方法。 常量池中的合成方法和方法句柄都包含genericstypes(如果lambda使用types,或者在您的示例中是显式的)。

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

不幸的是,生成的类不存储通用签名(它可以),所以你不能使用通常的reflection方法,让您避开擦除

对于一个普通的类,你可以使用Class.getResource(ClassName + ".class") .class Class.getResource(ClassName + ".class")检查字节码,但对于使用Unsafe定义的匿名类,你是不走运的。 但是,您可以使LambdaMetaFactory使用JVM参数进行转储:

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

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

这不幸的是它得到了hackie:

使用reflection,我们可以调用Class.getConstantPool ,然后访问MethodRefInfo来获取types描述符。 然后我们可以使用ASM来parsing它并返回参数types。 把它放在一起:

 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); 

更新与乔纳森的build议

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

  • 它使用未logging的方法和类
  • 它非常容易在JDK中进行代码更改
  • 它不保留genericstypes,所以如果你将List <String>传递给lambda,它将以List的forms出现

参数化types信息仅在运行时可用于绑定的代码元素 – 即专门编译为types。 Lambda做同样的事情,但是因为你的Lambda是一种方法而不是一种types的,所以没有types来捕获这些信息。

考虑以下:

 import java.util.Arrays; import java.util.function.Function; public class Erasure { static class RetainedFunction implements Function<Integer,String> { public String apply(Integer t) { return String.valueOf(t); } } public static void main(String[] args) throws Exception { Function<Integer,String> f0 = new RetainedFunction(); Function<Integer,String> f1 = new Function<Integer,String>() { public String apply(Integer t) { return String.valueOf(t); } }; Function<Integer,String> f2 = String::valueOf; Function<Integer,String> f3 = i -> String.valueOf(i); for (Function<Integer,String> f : Arrays.asList(f0, f1, f2, f3)) { try { System.out.println(f.getClass().getMethod("apply", Integer.class).toString()); } catch (NoSuchMethodException e) { System.out.println(f.getClass().getMethod("apply", Object.class).toString()); } System.out.println(Arrays.toString(f.getClass().getGenericInterfaces())); } } } 

f0f1都保留它们的genericstypes信息,就像你所期望的那样。 但是因为它们是已经被擦除到Function<Object,Object>未绑定方法,所以f2f3不会。

我最近增加了对TypeToolsparsinglambdatypes参数的支持 。 例如:

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

parsing的types参数如预期的那样:

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

处理一个通过的lambda:

 public void call(Callable<?> c) { // Assumes c is a lambda Class<?> callableType = TypeResolver.resolveRawArguments(Callable.class, c.getClass()); } 

注意:底层实现使用由@danielbodart概述的ConstantPool方法,该方法在Oracle JDK和OpenJDK(可能还有其他)上工作。

我find了一个可串行化的lambdaexpression式的方法。 我所有的lambda都是可串行化的。

谢谢,Holger,指点我的SerializedLambda

通用参数在lambda的综合静态方法中被捕获,并且可以从那里被检索。 find实现lambda的静态方法是可能的从SerializedLambda的信息

步骤如下:

  1. 通过为所有可序列化的lambdas自动生成的写入replace方法获取SerializedLambda
  2. find包含lambda实现的类(作为一个综合静态方法)
  3. 获取合成静态方法的java.lang.reflect.Method
  4. 从该Method获取genericstypes

更新:显然,这不适用于所有编译器。 我已经尝试了Eclipse Luna(works)的编译器和Oracle javac(不工作)。


 // sample how to use public static interface SomeFunction<I, O> extends java.io.Serializable { List<O> applyTheFunction(Set<I> value); } public static void main(String[] args) throws Exception { SomeFunction<Double, Long> lambda = (set) -> Collections.singletonList(set.iterator().next().longValue()); SerializedLambda sl = getSerializedLambda(lambda); Method m = getLambdaMethod(sl); System.out.println(m); System.out.println(m.getGenericReturnType()); for (Type t : m.getGenericParameterTypes()) { System.out.println(t); } // prints the following // (the method) private static java.util.List test.ClassWithLambdas.lambda$0(java.util.Set) // (the return type, including *Long* as the generic list type) java.util.List<java.lang.Long> // (the parameter, including *Double* as the generic set type) java.util.Set<java.lang.Double> 

 // getting the SerializedLambda public static SerializedLambda getSerializedLambda(Object function) { if (function == null || !(function instanceof java.io.Serializable)) { throw new IllegalArgumentException(); } for (Class<?> clazz = function.getClass(); clazz != null; clazz = clazz.getSuperclass()) { try { Method replaceMethod = clazz.getDeclaredMethod("writeReplace"); replaceMethod.setAccessible(true); Object serializedForm = replaceMethod.invoke(function); if (serializedForm instanceof SerializedLambda) { return (SerializedLambda) serializedForm; } } catch (NoSuchMethodError e) { // fall through the loop and try the next class } catch (Throwable t) { throw new RuntimeException("Error while extracting serialized lambda", t); } } throw new Exception("writeReplace method not found"); } 

 // getting the synthetic static lambda method public static Method getLambdaMethod(SerializedLambda lambda) throws Exception { String implClassName = lambda.getImplClass().replace('/', '.'); Class<?> implClass = Class.forName(implClassName); String lambdaName = lambda.getImplMethodName(); for (Method m : implClass.getDeclaredMethods()) { if (m.getName().equals(lambdaName)) { return m; } } throw new Exception("Lambda Method not found"); }