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

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

问题描述

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

尽管Java在编译器之后丢弃了很多泛型类型信息,但泛型超类的子类(和匿名子类)而通用超接口确实保留了这种类型的信息。通过反射,这些类型是可访问的。在下面的示例(case 1)中,反射告诉我< MapFunction MyMapper 实现>将 java.lang.Integer 绑定到泛型类型参数 T



即使对于它们本身是泛型的子类,也有一些方法可以找出绑定到泛型参数的东西,如果其他东西已知的话。考虑下面例子中的 case 2 IdentityMapper ,其中 F T 绑定到相同的类型。当我们知道这一点时,如果我们知道参数类型 T (在我的例子中,我们知道类型为 F )。

现在的问题是,我如何才能实现类似于Java 8 lambda的东西?由于它们实际上不是通用超级接口的常规子类,所以上述方法不起作用。
具体而言,我可以发现 parseLambda java.lang.Integer 绑定到 T ,并且 identityLambda 将其绑定到 F 和<$ c $ PS:理论上应该可以反编译lambda代码,然后使用嵌入式编译器(如JDT)和挖掘其类型推断。我希望有一个更简单的方法来做到这一点; - ) $ b

  / ** 
*超级接口。
* /
public interface MapFunction< F,T> {

T图(F值);
}

/ **
*案例1:非泛型子类。
* /
public class MyMapper实现MapFunction< String,Integer> {

public Integer map(String value){
return Integer.valueOf(value);
}
}

/ **
*通用子类
* /
public class IdentityMapper< E>实现MapFunction< E,E> {

公共E地图(E值){
返回值;
}

}

/ **
*通过lambda实例化
* /

public MapFunction<字符串,整数> parseLambda =(String str) - > {return Integer.valueOf(str); }

public MapFunction< E,E> identityLambda =(value) - > {返回值; }

$ b public static void main(String [] args)
{
// case 1
getReturnType(MyMapper.class); // - >返回java.lang.Integer

// case 2
getReturnTypeRelativeToParameter(IdentityMapper.class,String.class); // - >返回java.lang.String
}

private static Class<?> getReturnType(Class<> implementsClass)
{
Type superType = implementsClass.getGenericInterfaces()[0];

if(superType instanceof ParameterizedType){
ParameterizedType parameterizedType =(ParameterizedType)superType;
return(Class <?>)parameterizedType.getActualTypeArguments()[1];
}
else else null;
}

private static Class<?> getReturnTypeRelativeToParameter(Class<> implementsClass,Class<> parameterType)
{
Type superType = implementsClass.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 {
//计算出合成返回类型的逻辑
}
}

return null;
}


解决方案

为可串行化的lambda表达。我的所有lambda表达式都是可序列化的,这对我有用。



谢谢Holger,我指的是 SerializedLambda / p>

通用参数是在lambda的综合静态方法中捕获的,并且可以从那里检索。使用来自 SerializedLambda



的信息找到实现lambda的静态方法。步骤如下:
$ b


  1. 通过为所有可串行化lambdas自动生成的写入替换方法获取SerializedLambda
  2. 找到包含lambda实现的类(作为一个综合静态方法)
  3. 获取 java.lang.reflect.Method 合成静态方法
    从该方法获取泛型






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




  //示例如何使用
public static interface SomeFunction< I,O>扩展java.io.Serializable {

列表< 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);
方法m = getLambdaMethod(sl);

System.out.println(m);
System.out.println(m.getGenericReturnType());
for(Type t:m.getGenericParameterTypes()){
System.out.println(t);
}

//输出以下
//(方法)private static java.util.List test.ClassWithLambdas.lambda $ 0(java.util.Set)
//(返回类型,包括* Long *作为通用列表类型)java.util.List< java.lang.Long>
//(参数,包括* Double *作为通用集合类型)java.util.Set< java.lang.Double>






  / /获取SerializedLambda 
public static SerializedLambda getSerializedLambda(Object function){
if(function == null ||!(function instanceof java.io.Serializable)){
throw new IllegalArgumentException() ; (Class <?> clazz = function.getClass(); clazz!= null; clazz = clazz.getSuperclass()){
try {


$ b} $ b方法replaceMethod = clazz.getDeclaredMethod(writeReplace);
replaceMethod.setAccessible(true);
Object serializedForm = replaceMethod.invoke(function);

if(serializedForm instanceof SerializedLambda){
return(SerializedLambda)serializedForm;


catch(NoSuchMethodError e){
//循环并尝试下一课
}
catch(Throwable t){
抛出新的RuntimeException(提取序列化lambda时出错,t);
}
}

抛出新的异常(writeReplace method not found);






  //获取综合静态lambda方法
public static Method getLambdaMethod(SerializedLambda lambda)throws Exception {
String string implClassName = lambda.getImplClass()。replace('/','。') ;
Class<?> implClass = Class.forName(implClassName);

字符串lambdaName = lambda.getImplMethodName(); (方法m:implClass.getDeclaredMethods()){
if(m.getName()。equals(lambdaName)){
return m;


}
}

抛出新的异常(Lambda Method not found);
}


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.

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.

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

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: 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;
}

解决方案

I have found a way of doing it for serializable lambdas. All my lambdas are serializable, to that works.

Thanks, Holger, for pointing me to the 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

The steps are as follows:

  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


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 Lambdas上的反射类型推断的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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