Reflection type inference on Java 8 Lambdas
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.
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