Java 8 Lambda 上的反射类型推断 [英] Reflection type inference on Java 8 Lambdas
问题描述
我正在尝试使用 Java 8 中的新 Lambda,我正在寻找一种方法来使用对 lambda 类的反射来获取 lambda 函数的返回类型.我对 lambda 实现通用超接口的情况特别感兴趣.在下面的代码示例中,MapFunction
是通用超接口,我正在寻找一种方法来找出绑定到通用参数 T
的类型.
I was experimenting with the new Lambdas in Java 8, and I am looking for a way to use reflection on the lambda classes to get the return type of a lambda function. I am especially interested in cases where the lambda implements a generic superinterface. In the code example below, MapFunction<F, T>
is the generic superinterface, and I am looking for a way to find out what type binds to the generic parameter T
.
虽然 Java 在编译器之后丢弃了很多泛型类型信息,但泛型超类和泛型超接口的子类(和匿名子类)确实保留了这些类型信息.通过反射,这些类型是可以访问的.在下面的例子中,(case 1),反射告诉我 MapFunction
的 MyMapper
实现绑定了 java.lang.Integer
到泛型类型参数 T
.
While Java throws away a lot of generic type information after the compiler, subclasses (and anonymous subclasses) of generic superclasses and generic superinterfaces did preserve that type information. Via reflection, these types were accessible. In the example below (case 1), reflection tells my that the MyMapper
implementation of MapFunction
binds java.lang.Integer
to the generic type parameter T
.
即使对于本身是泛型的子类,如果知道其他一些参数,也可以通过某些方法找出绑定到泛型参数的内容.考虑下面示例中的 case 2,IdentityMapper
,其中 F
和 T
绑定到相同的类型.当我们知道这一点时,如果我们知道参数类型 T
(在我的例子中我们知道),我们就知道类型 F
.
Even for subclasses that are themselves generic, there are certain means to find out what binds to a generic parameter, if some others are known. Consider case 2 in the example below, the IdentityMapper
where both F
and T
bind to the same type. When we know that, we know the type F
if we know the parameter type T
(which in my case we do).
现在的问题是,我如何才能为 Java 8 lambda 实现类似的功能?由于它们实际上不是通用超接口的常规子类,因此上述方法不起作用.具体来说,我能不能弄清楚 parseLambda
将 java.lang.Integer
绑定到 T
,而 identityLambda
绑定F
和 T
一样吗?
The question is now, how can I realize something similar for the Java 8 lambdas? Since they are actually not regular subclasses of the generic superinterface, the above described method does not work.
Specifically, can I figure out that the parseLambda
binds java.lang.Integer
to T
, and the identityLambda
binds the same to F
and T
?
PS:理论上应该可以反编译 lambda 代码,然后使用嵌入式编译器(如 JDT)并利用其类型推断.我希望有一种更简单的方法来做到这一点;-)
PS: In theory it should possible to decompile the lambda code and then use an embedded compiler (like the JDT) and tap into its type inference. I hope that there is a simpler way to do this ;-)
/**
* 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 表达式都是可序列化的,因此可以正常工作.
I have found a way of doing it for serializable lambdas. All my lambdas are serializable, to that works.
感谢 Holger,他为我指明了 SerializedLambda
.
Thanks, Holger, for pointing me to the SerializedLambda
.
泛型参数在 lambda 的合成静态方法中捕获,可以从那里检索.使用 SerializedLambda
The generic parameters are captured in the lambda's synthetic static method and can be retrieved from there. Finding the static method that implements the lambda is possible with the information from the SerializedLambda
步骤如下:
- 通过为所有可序列化 lambda 自动生成的写入替换方法获取 SerializedLambda
- 查找包含 lambda 实现的类(作为合成静态方法)
- 获取合成静态方法的
java.lang.reflect.Method
- 从那个
Method
获取泛型类型
- Get the SerializedLambda via the write replacement method that is auto-generated for all serializable lambdas
- Find the class that contains the lambda implementation (as a synthetic static method)
- Get the
java.lang.reflect.Method
for the synthetic static method - Get generic types from that
Method
<小时>
更新: 显然,这不适用于所有编译器.我已经用 Eclipse Luna(有效)和 Oracle javac(无效)的编译器试过了.
UPDATE: Apparently, this does not work with all compilers. I have tried it with the compiler of Eclipse Luna (works) and the Oracle javac (does not work).
// 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");
}
这篇关于Java 8 Lambda 上的反射类型推断的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!