Java 8 Lambda 上的反射类型推断 [英] Reflection type inference on Java 8 Lambdas

查看:42
本文介绍了Java 8 Lambda 上的反射类型推断的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试使用 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),反射告诉我 MapFunctionMyMapper 实现绑定了 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 2IdentityMapper,其中 FT 绑定到相同的类型.当我们知道这一点时,如果我们知道参数类型 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 实现类似的功能?由于它们实际上不是通用超接口的常规子类,因此上述方法不起作用.具体来说,我能不能弄清楚 parseLambdajava.lang.Integer 绑定到 T,而 identityLambda 绑定FT 一样吗?

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

步骤如下:

  1. 通过为所有可序列化 lambda 自动生成的写入替换方法获取 SerializedLambda
  2. 查找包含 lambda 实现的类(作为合成静态方法)
  3. 获取合成静态方法的java.lang.reflect.Method
  4. 从那个Method获取泛型类型
  1. Get the SerializedLambda via the write replacement method that is auto-generated for all serializable lambdas
  2. Find the class that contains the lambda implementation (as a synthetic static method)
  3. Get the java.lang.reflect.Method for the synthetic static method
  4. 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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆