55

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;
}
Paul Bellora
  • 54,340
  • 18
  • 130
  • 181
Stephan Ewen
  • 2,311
  • 1
  • 17
  • 14
  • 1
    Just a minor comment: `public MapFunction parseLambda = (String str) -> { return Integer.valueOf(str); }` can be written as `public MapFunction parseLambda = str -> Integer.valueOf(str);`. – skiwi Feb 20 '14 at 09:58
  • I don't think it is possible. It seems Project Lambda authors tried to prevent reflection for some reason as stack trace gives no info about method names, and even method references don't give method name by reflection. – aalku Feb 21 '14 at 18:19
  • 1
    @skiwi: I've not yet written any Java 8 code but my understanding is that `public MapFunction parseLambda = Integer::valueOf;` would be effectively equivalent. – Joachim Sauer Dec 09 '14 at 15:41
  • 1
    @JoachimSauer That's correct – skiwi Dec 09 '14 at 15:42

6 Answers6

19

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.

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.

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.

gouessej
  • 3,640
  • 3
  • 33
  • 67
Holger
  • 285,553
  • 42
  • 434
  • 765
  • 13
    Really bad idea. Serialized lambdas have significantly higher performance costs compared to nonserializable lambdas, and are less secure (serialization is inherently a public hidden constructor to behavior and/or captured data that the programmer might have intended to stay private.) – Brian Goetz May 27 '14 at 14:16
  • The method signature and the instantiated interface type aren't enough to determine which method is called in the lambda. Moreover, I often get the compile error "Code too large" when trying to use this hack. Finally, it's possible to get the first captured argument by using the reflection without needing to serialize the lambda. – gouessej Sep 14 '20 at 14:18
  • 1
    @gouessej this question never was about getting the first captured argument, so I don’t see the relevance of this part of your comment. And I don’t understand what you are trying to say with your first sentence, of course, methods are identified by their name and signature. Nothing else. The “code too large” error is an entirely different thing. Considering Brian Goetz’ warning, you should not be surprised. Using it for production code is not recommended, overusing it, even in one class, hits serious limitations. There’s one synthetic method for deserializing all lambdas defined in the class. – Holger Sep 15 '20 at 07:31
  • @Holger Serializing the lambda doesn't help to obtain much more information than using plain reflection on the field "arg$1" which contains the instance called in the lambda. When I use your very last suggestion on '(final String s) -> a.b(s)', the serialized lambda doesn't contain 'a.b'. – gouessej Sep 16 '20 at 09:11
  • 1
    @gouessej of course not. That never was the task of this Q&A. – Holger Sep 16 '20 at 11:37
15

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

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.

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:

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.

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:

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:

  • It uses undocumented methods and classes
  • It is extremely vulnerable to code changes in the JDK
  • It doesn't preserve the generic types, so if you pass List<String> into a lambda it will come out as List
  • Wow, this looks like serious business ;-) The solution outlined by me above is quite a bit simpler, but unfortunately does not work with the OpenJDK compiler (or the Oracle compiler), but only with the Eclipse compiler - the latter stores the generic method signature in the class file. The Method object can use that signature to get the generic parameter types and the generic return type. Submitting a patch to the OpenJDK team to include that signature in a similar way would be the perfect solution. Is it possible to make such suggestions? – Stephan Ewen Sep 08 '14 at 17:56
  • 3
    This example will work for `java.util.function.Function` but not for some other functional interfaces since the member ref is located at different indexes. For Oracle JDK and Open JDK the member ref can be reliably obtained via `constantPool.getMemberRefInfoAt(constantPool.size() - 2)` – Jonathan Dec 08 '14 at 18:14
  • @danielbodart "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" - Why? You can reflect the generic type of an anonomous innerclass, but not lambda :( This is a bug in my opinion. – mjs Dec 18 '14 at 14:47
  • @danielbodart Is it possible to get the generic type, as one can with a class or anonymous inner class using getActualTypeArguments? Tehre is no information when switched to a lambda. – mjs Dec 18 '14 at 14:49
  • Actually, I am not after the method parameter, but the exception type that is thrown and defined by the generic type of the interface. MyInterface { void call() throws E; } () { doSomthingThatThrowsE() } – mjs Dec 18 '14 at 15:19
  • 6
    @danielbodart I have written a patch that adds the generic signature to the synthetic lambda methods when using a compiler flag. I'm looking for supporters for it: http://mail.openjdk.java.net/pipermail/compiler-dev/2015-January/009220.html – twalthr Jan 09 '15 at 12:33
14

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

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

The resolved type args are as expected:

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

To handle a passed 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).

Jonathan
  • 5,027
  • 39
  • 48
  • Wait, I haven't checked your link it, but if you can do this, then I love you! – mjs Dec 18 '14 at 14:50
  • I looked at, but Jonathan, what I am looking for is to get the generic type of a passed lambda. I am not sure I understand the API, or that it can do this. interface Lamb { ... } (String str) -> {} ... do you understand what I am after? – mjs Dec 18 '14 at 15:05
  • Actually, I am not after the method String parameter, but the exception type that is thrown and defined by the generic type. MyInterface { void call() throws E; } ... () { doSomthingThatThrowsE() } – mjs Dec 18 '14 at 15:25
  • I updated my post to include how to resolve type args from a passed lambda. Re: the exception type, you may be able to get that somehow via reflection, but it's not something TypeTools does right now. – Jonathan Dec 18 '14 at 17:52
  • 4
    Seems getting return type of this lambda expression `() -> (List)null` will only get `java.util.List`, which is without the nested generic type in the `List`. – JasonMing May 18 '16 at 05:56
11

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.

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.

MrPotes
  • 356
  • 2
  • 10
  • What if `Function f3 = (Integer) i -> String.valueOf(i);` ? It seems that parameterized types are still not bound. – Hao Ren Jul 29 '15 at 15:41
  • Correct - casting the parameter still doesn't cause the function to become part of a compiled type, so it is still erased. You have to make the function some sort of class such as the anonymous and inner classes illustrated as f0 and f1. – MrPotes Aug 02 '15 at 20:51
9

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");
}
Stephan Ewen
  • 2,311
  • 1
  • 17
  • 14
  • 2
    Note that `SerializedLambda.getImplMethodSignature()` already provides the type signature of the lambda’s synthetic method. It’s better to use that as the method name is not guaranteed to be unique. – Holger Sep 08 '14 at 08:38
  • So what is it that happens on Oracle Javac? – Antti Haapala -- Слава Україні Oct 31 '14 at 08:20
  • 2
    FYI: The Eclipse compiler supported this unintentional. Version 4.5 M4 will allow it with help of a compiler option "-genericsignature" (https://bugs.eclipse.org/bugs/show_bug.cgi?id=449063) – twalthr Dec 12 '14 at 11:47
  • 1
    In case anyone else is in desperate search for a way to reliable identify multiple instances of a given method reference, this answer is pure gold. Method replaceMethod = clazz.getDeclaredMethod("writeReplace"); replaceMethod.setAccessible(true); Object serializedForm = replaceMethod.invoke(function); if (serializedForm instanceof SerializedLambda) { return ((SerializedLambda)serializedForm).getImplMethodName(); } – Ajax Jul 17 '16 at 07:24
  • You will obviously need to use serializable lambdas for this to work, but, if you can constrain them to that form, this will do whatcha need :-) – Ajax Jul 17 '16 at 07:26
  • Oh, and you'll also want to add the System.identityHashCode of all captured args. :-) – Ajax Jul 17 '16 at 07:40
-1

There is a way to do this if the method reference references an method on an interface. Create a dynamic proxy that implements the interface, and invoke the method on the proxy. The proxy's invocation handler can then introspect on the Method object that is passed in.

For example:

Function<MyInterface, String> fn = MyInterface::toString;

Class<MyInterface> type = MyInterface.class;
String[] name = new String[1];
fn.apply(type.cast(Proxy.newProxyInstance(type.getClassLoader(), new Class<?>[] {type}, (p, m, a) -> {
  name[0] = m.getName();
  return null;
)));

System.out.println(name[0]); // will print out "toString"
Tyler2P
  • 2,324
  • 26
  • 22
  • 31