Java 8的反射类型推断

时间:2021-02-23 17:04:42

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 8中试用了新的Lambdas,我正在寻找一种方法来使用lambda类的反射来获得lambda函数的返回类型。我特别感兴趣的是lambda实现通用超接口的情况。在下面的代码示例中,MapFunction 是泛型超接口,我正在寻找一种方法来找出与泛型参数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.

虽然Java在编译器之后丢弃了大量泛型类型信息,但泛型超类和泛型超接口的子类(和匿名子类)确实保留了该类型信息。通过反射,这些类型是可访问的。在下面的示例(案例1)中,反射告诉我,MapFunction的MyMapper实现绑定java.lang。通用类型参数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).

即使对于本身是泛型的子类,如果知道其他一些泛型参数,也有确定的方法来确定绑定到泛型参数的是什么。考虑下面示例中的情形2,标识符映射器,其中F和T都绑定到相同的类型。当我们知道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?

现在的问题是,我怎么才能意识到Java 8 lambdas有什么相似之处呢?由于它们实际上不是泛型超接口的常规子类,因此上述方法不起作用。具体地说,我能算出parseLambda绑定java.lang吗?整数到T,而恒等与F和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 ;-)

PS:理论上应该可以对lambda代码进行反编译,然后使用嵌入式编译器(如JDT)并利用其类型推断。我希望有一个更简单的方法来做这件事;

/**
 * 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;
}

5 个解决方案

#1


15  

The exact decision how to map lambda code to interface implementations is left to the actual runtime environment. In principle, all lambdas implementing the same raw interface could share a single runtime class just like MethodHandleProxies does. Using different classes for specific lambdas is an optimization performed by the actual LambdaMetafactory implementation but not a feature intended to aid debugging or Reflection.

如何将lambda代码映射到接口实现的确切决定将留给实际的运行时环境。原则上,所有实现相同原始接口的lambdas都可以共享一个运行时类,就像methodhandleproxy所做的那样。针对特定的lambdas使用不同的类是由实际的LambdaMetafactory实现执行的优化,而不是用来帮助调试或反射的特性。

So even if you find more detailed information in the actual runtime class of a lambda interface implementation it will be an artifact of the currently used runtime environment which might not be available in different implementation or even other versions of your current environment.

因此,即使您在lambda接口实现的实际运行时类中找到了更详细的信息,它也将是当前使用的运行时环境的一个工件,可能在不同的实现中,甚至在当前环境的其他版本中都无法使用。

If the lambda is Serializable you can use the fact that the serialized form contains the method signature of the instantiated interface type to puzzle the actual type variable values together.

如果lambda是可序列化的,那么可以使用序列化表单包含实例化接口类型的方法签名这一事实来迷惑实际的类型变量值。

#2


11  

This is currently possible to solve but only in a pretty hackie way, but let me first explain a few things:

这目前是可以解决的,但只是用一种相当陈腐的方式,但是让我先解释一些事情:

When you write a lambda, the compiler inserts a dynamic invoke instruction pointing to the LambdaMetafactory and a private static synthetic method with the body of the lambda. The synthetic method and the method handle in the constant pool both contain the generic type (if the lambda uses the type or is explicit as in your examples).

当您编写lambda时,编译器将插入指向LambdaMetafactory的动态调用指令和一个带有lambda主体的私有静态合成方法。常量池中的合成方法和方法句柄都包含泛型类型(如果lambda使用类型或如示例中那样显式)。

Now at runtime the LambdaMetaFactory is called and a class is generated using ASM that implements the functional interface and the body of the method then calls the private static method with any arguments passed. It is then injected into the original class using Unsafe.defineAnonymousClass (see John Rose post) so it can access the private members etc.

现在,在运行时调用LambdaMetaFactory并使用ASM生成一个类,它实现了功能接口和方法的主体,然后调用私有的静态方法,并通过任何参数。然后使用unsafe.define匿名类(见John Rose post)将它注入到原始类中,这样它就可以访问私有成员等等。

Unfortunately the generated Class does not store the generic signatures (it could) so you can't use the usual reflection methods that allow you to get around erasure

不幸的是,生成的类没有存储泛型签名(它可以),所以您不能使用通常的反射方法来避免擦除

For a normal Class you could inspect the bytecode using Class.getResource(ClassName + ".class") but for anonymous classes defined using Unsafe you are out of luck. However you can make the LambdaMetaFactory dump them out with the JVM argument:

对于普通类,可以使用类检查字节码。getResource(类名+“.class”),但是对于使用不安全定义的匿名类,您就不走运了。但是,您可以让LambdaMetaFactory使用JVM参数将它们转储出来:

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

By looking at the dumped class file (using javap -p -s -v), one can see that it does indeed call the static method. But the problem remains how to get the bytecode from within Java itself.

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

This unfortunately is where it gets hackie:

不幸的是,这正是它被人遗忘的地方:

Using reflection we can call Class.getConstantPool and then access the MethodRefInfo to get the type descriptors. We can then use ASM to parse this and return the argument types. Putting it all together:

使用反射我们可以调用类。获取constantpool,然后访问方法drefinfo以获取类型描述符。然后我们可以使用ASM来解析它并返回参数类型。把它放在一起:

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

Updated with jonathan's suggestion

更新乔纳森的建议

Now ideally the classes generated by LambdaMetaFactory should store the generic type signatures (I might see if I can submit a patch to the OpenJDK) but currently this is the best we can do. The code above has the following problems:

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

  • It uses undocumented methods and classes
  • 它使用未文档化的方法和类
  • It is extremely vulnerable to code changes in the JDK
  • 它非常容易受到JDK中的代码更改的影响
  • It doesn't preserve the generic types, so if you pass List<String> into a lambda it will come out as List
  • 它不保存泛型类型,因此,如果您将列表 放入一个lambda中,它将会显示为List。

#3


10  

Parameterized type information is only available at runtime for elements of code that are bound - that is, specifically compiled into a type. Lambdas do the same thing, but as your Lambda is de-sugared to a method rather than to a type, there is no type to capture that information.

参数化类型信息仅在运行时对绑定的代码元素可用——也就是说,特定地编译为类型。Lambdas做同样的事情,但是由于您的Lambda被分解为方法而不是类型,所以没有类型来捕获该信息。

Consider the following:

考虑以下:

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 and f1 both retain their generic type information, as you'd expect. But as they're unbound methods that have been erased to Function<Object,Object>, f2 and f3 do not.

f0和f1都保留了它们的泛型类型信息,正如您所期望的那样。但由于它们是未绑定的方法,已被删除以函数 、f2和f3不会。 ,所以对象>

#4


7  

I recently added support for resolving lambda type arguments to TypeTools. Ex:

我最近添加了对解决lambda类型参数的支持。例:

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

The resolved type args are as expected:

解析式args如预期:

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

To handle a passed lambda:

要处理已传递的lambda:

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

Note: The underlying implementation uses the ConstantPool approach outlined by @danielbodart which is known to work on Oracle JDK and OpenJDK (and possibly others).

注意:底层实现使用了@danielbodart提出的ConstantPool方法,众所周知,这种方法可以用于Oracle JDK和OpenJDK(可能还有其他)。

#5


4  

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

我找到了一种方法来处理可序列化的lambdas。我所有的lambda都是可序列化的。

Thanks, Holger, for pointing me to the SerializedLambda.

谢谢你,霍尔格,给我指的是序列化的lambda。

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的静态方法

The steps are as follows:

步骤如下:

  1. Get the SerializedLambda via the write replacement method that is auto-generated for all serializable lambdas
  2. 通过为所有可序列化的lambdas自动生成的write替换方法获取SerializedLambda
  3. Find the class that contains the lambda implementation (as a synthetic static method)
  4. 查找包含lambda实现的类(作为一个合成的静态方法)
  5. Get the java.lang.reflect.Method for the synthetic static method
  6. 得到数组。综合静态方法的方法
  7. Get generic types from that Method
  8. 从该方法获得泛型类型。

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

更新:显然,这并不适用于所有编译器。我尝试过Eclipse Luna (works)的编译器和Oracle javac (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");
}

#1


15  

The exact decision how to map lambda code to interface implementations is left to the actual runtime environment. In principle, all lambdas implementing the same raw interface could share a single runtime class just like MethodHandleProxies does. Using different classes for specific lambdas is an optimization performed by the actual LambdaMetafactory implementation but not a feature intended to aid debugging or Reflection.

如何将lambda代码映射到接口实现的确切决定将留给实际的运行时环境。原则上,所有实现相同原始接口的lambdas都可以共享一个运行时类,就像methodhandleproxy所做的那样。针对特定的lambdas使用不同的类是由实际的LambdaMetafactory实现执行的优化,而不是用来帮助调试或反射的特性。

So even if you find more detailed information in the actual runtime class of a lambda interface implementation it will be an artifact of the currently used runtime environment which might not be available in different implementation or even other versions of your current environment.

因此,即使您在lambda接口实现的实际运行时类中找到了更详细的信息,它也将是当前使用的运行时环境的一个工件,可能在不同的实现中,甚至在当前环境的其他版本中都无法使用。

If the lambda is Serializable you can use the fact that the serialized form contains the method signature of the instantiated interface type to puzzle the actual type variable values together.

如果lambda是可序列化的,那么可以使用序列化表单包含实例化接口类型的方法签名这一事实来迷惑实际的类型变量值。

#2


11  

This is currently possible to solve but only in a pretty hackie way, but let me first explain a few things:

这目前是可以解决的,但只是用一种相当陈腐的方式,但是让我先解释一些事情:

When you write a lambda, the compiler inserts a dynamic invoke instruction pointing to the LambdaMetafactory and a private static synthetic method with the body of the lambda. The synthetic method and the method handle in the constant pool both contain the generic type (if the lambda uses the type or is explicit as in your examples).

当您编写lambda时,编译器将插入指向LambdaMetafactory的动态调用指令和一个带有lambda主体的私有静态合成方法。常量池中的合成方法和方法句柄都包含泛型类型(如果lambda使用类型或如示例中那样显式)。

Now at runtime the LambdaMetaFactory is called and a class is generated using ASM that implements the functional interface and the body of the method then calls the private static method with any arguments passed. It is then injected into the original class using Unsafe.defineAnonymousClass (see John Rose post) so it can access the private members etc.

现在,在运行时调用LambdaMetaFactory并使用ASM生成一个类,它实现了功能接口和方法的主体,然后调用私有的静态方法,并通过任何参数。然后使用unsafe.define匿名类(见John Rose post)将它注入到原始类中,这样它就可以访问私有成员等等。

Unfortunately the generated Class does not store the generic signatures (it could) so you can't use the usual reflection methods that allow you to get around erasure

不幸的是,生成的类没有存储泛型签名(它可以),所以您不能使用通常的反射方法来避免擦除

For a normal Class you could inspect the bytecode using Class.getResource(ClassName + ".class") but for anonymous classes defined using Unsafe you are out of luck. However you can make the LambdaMetaFactory dump them out with the JVM argument:

对于普通类,可以使用类检查字节码。getResource(类名+“.class”),但是对于使用不安全定义的匿名类,您就不走运了。但是,您可以让LambdaMetaFactory使用JVM参数将它们转储出来:

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

By looking at the dumped class file (using javap -p -s -v), one can see that it does indeed call the static method. But the problem remains how to get the bytecode from within Java itself.

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

This unfortunately is where it gets hackie:

不幸的是,这正是它被人遗忘的地方:

Using reflection we can call Class.getConstantPool and then access the MethodRefInfo to get the type descriptors. We can then use ASM to parse this and return the argument types. Putting it all together:

使用反射我们可以调用类。获取constantpool,然后访问方法drefinfo以获取类型描述符。然后我们可以使用ASM来解析它并返回参数类型。把它放在一起:

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

Updated with jonathan's suggestion

更新乔纳森的建议

Now ideally the classes generated by LambdaMetaFactory should store the generic type signatures (I might see if I can submit a patch to the OpenJDK) but currently this is the best we can do. The code above has the following problems:

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

  • It uses undocumented methods and classes
  • 它使用未文档化的方法和类
  • It is extremely vulnerable to code changes in the JDK
  • 它非常容易受到JDK中的代码更改的影响
  • It doesn't preserve the generic types, so if you pass List<String> into a lambda it will come out as List
  • 它不保存泛型类型,因此,如果您将列表 放入一个lambda中,它将会显示为List。

#3


10  

Parameterized type information is only available at runtime for elements of code that are bound - that is, specifically compiled into a type. Lambdas do the same thing, but as your Lambda is de-sugared to a method rather than to a type, there is no type to capture that information.

参数化类型信息仅在运行时对绑定的代码元素可用——也就是说,特定地编译为类型。Lambdas做同样的事情,但是由于您的Lambda被分解为方法而不是类型,所以没有类型来捕获该信息。

Consider the following:

考虑以下:

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 and f1 both retain their generic type information, as you'd expect. But as they're unbound methods that have been erased to Function<Object,Object>, f2 and f3 do not.

f0和f1都保留了它们的泛型类型信息,正如您所期望的那样。但由于它们是未绑定的方法,已被删除以函数 、f2和f3不会。 ,所以对象>

#4


7  

I recently added support for resolving lambda type arguments to TypeTools. Ex:

我最近添加了对解决lambda类型参数的支持。例:

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

The resolved type args are as expected:

解析式args如预期:

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

To handle a passed lambda:

要处理已传递的lambda:

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

Note: The underlying implementation uses the ConstantPool approach outlined by @danielbodart which is known to work on Oracle JDK and OpenJDK (and possibly others).

注意:底层实现使用了@danielbodart提出的ConstantPool方法,众所周知,这种方法可以用于Oracle JDK和OpenJDK(可能还有其他)。

#5


4  

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

我找到了一种方法来处理可序列化的lambdas。我所有的lambda都是可序列化的。

Thanks, Holger, for pointing me to the SerializedLambda.

谢谢你,霍尔格,给我指的是序列化的lambda。

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的静态方法

The steps are as follows:

步骤如下:

  1. Get the SerializedLambda via the write replacement method that is auto-generated for all serializable lambdas
  2. 通过为所有可序列化的lambdas自动生成的write替换方法获取SerializedLambda
  3. Find the class that contains the lambda implementation (as a synthetic static method)
  4. 查找包含lambda实现的类(作为一个合成的静态方法)
  5. Get the java.lang.reflect.Method for the synthetic static method
  6. 得到数组。综合静态方法的方法
  7. Get generic types from that Method
  8. 从该方法获得泛型类型。

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

更新:显然,这并不适用于所有编译器。我尝试过Eclipse Luna (works)的编译器和Oracle javac (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");
}