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 ,其中F
和T
绑定到相同types的IdentityMapper
。 当我们知道的时候,如果我们知道参数typesT
(在我的情况下,我们知道的话),我们知道typesF
现在的问题是,我怎样才能实现Java 8 lambdas类似的东西呢? 由于它们实际上不是generics超级界面的常规子类,因此上述方法不起作用。 具体来说,我可以找出parseLambda
将java.lang.Integer
绑定到T
, identityLambda
parseLambda
绑定到F
和T
?
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())); } } }
f0
和f1
都保留它们的genericstypes信息,就像你所期望的那样。 但是因为它们是已经被擦除到Function<Object,Object>
未绑定方法,所以f2
和f3
不会。
我最近增加了对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
的信息
步骤如下:
- 通过为所有可序列化的lambdas自动生成的写入replace方法获取SerializedLambda
- find包含lambda实现的类(作为一个综合静态方法)
- 获取合成静态方法的
java.lang.reflect.Method
- 从该
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"); }