23

We have this functional interface:

public interface Consumer<T> {

    void accept(T t);

}

And I can use it like this:

.handle(Integer p -> System.out.println(p * 2));

How can we resolve the actual generic type of that lambda parameter in our code?

When we use it as an inline implementation it isn't so difficult to extract the Integer from the method of that class.

Do I miss anything? Or just Java doesn't support it for lambda classes ?

To be more clear:

That lambda is wrapped with MethodInvoker (in the mentioned handle), which in its execute(Message<?> message) extracts actual parameters for further reflection method invocation. Before that it converts provided arguments to target parameters using Spring's ConversionService.

The method handle in this case is some configurer before the real application work.

The different question, but with expectation for the solution for the same issue: Java: get actual type of generic method with lambda parameter

15
  • Not sure I understand; in your example you can do without Integer, the compiler does type inference here. Commented May 26, 2014 at 6:11
  • No. I really need to know that type: we have some converter subsystem which can convert "2" to int (the simplest sample), if it can determine the type of parameter. Otherwise we end up with java.lang.String cannot be cast to java.lang.Integer. Having that Java somehow understands the actual type. But how can I do that from code? Commented May 26, 2014 at 6:16
  • 1
    One more time: I want to know actual generic type of method argument. I can do that for classes, but can't for lambdas. That's all. Don't try to understand the logic of my application - it is Spring and it has powerfull Converter sub-system. E.g. I can write and register some UserToPersonConverter and just send the User to my consumer and it will be converted to the Person method parameter. It works with inline classes, but doesn't with lambdas Commented May 26, 2014 at 6:32
  • 2
    And what is being invoked through reflection? Please edit your question with all this relevant information. Commented May 26, 2014 at 6:52
  • 1
    You can find it in my project github.com/spring-projects/spring-integration-java-dsl. Where generics resolution is done via an additional Class<T> argument: github.com/spring-projects/spring-integration-java-dsl/blob/… and an internal solution LambdaMessageProcessor does the stuff for us. Nothing third-party, BTW: just Java as is in its current state... Commented Jul 24, 2015 at 14:07

2 Answers 2

14

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
Sign up to request clarification or add additional context in comments.

5 Comments

I've accepted this answer as it is enough smart and tricky. Anyway we decided to overcome the issue with additional param - see my linked question. Thanks
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)
I came across a few more corner cases where this doesn't work as is - involving type arguments resolved from certain method references, and from lambdas/method refs with boxed integers. See TypeTools for the updates.
@DanielWorthington-Bodart about "Now ideally the classes generated by LambdaMetaFactory should store the generic type signatures", did you follow up on this in OpenJDK? I have one issue where the generated synthetic method does not contain the generic signature, verified by looking at javap output.
There are fundamental flaws with the assumption that generic parameter types must be resolvable at runtime. Take for example, Function<String,String> f1 = Function.identity(); Function<Integer,Integer> f2 = Function.identity(); (the function returned by identity() is implemented as lambda expression, t -> t). In that regard, lambda expressions are not different to generic classes. So even in environments where this hack works, it only serves a certain fraction of functions.
1

Use a TypeRef to wrap the lambda expression.

@FunctionalInterface
interface TypeRef<T> {
    T create();

    default Class<?> getGenericType() {
        return create().getClass();
    }
}

public class Main {
    public static void main(String[] args) {
        System.out.println(getFactoryTypeParameter(() -> "hello"));
    }

    private static <T> Class<?> getFactoryTypeParameter(TypeRef<T> typeRef) {
        return typeRef.getGenericType();
    }
}

output:

class java.lang.String

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.