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; }
Type Inference means that the data type of any expression (e.g. method return type or parameter type) can be deduced automatically by the compiler. Groovy language is a good example of programming languages supporting Type Inference. Similarly, Java 8 Lambda expressions also support Type inference.
Lambda Expressions were added in Java 8. A lambda expression is a short block of code which takes in parameters and returns a value. Lambda expressions are similar to methods, but they do not need a name and they can be implemented right in the body of a method.
Characteristics of Lambda ExpressionOptional Type Declaration − There is no need to declare the type of a parameter. The compiler inferences the same from the value of the parameter. Optional Parenthesis around Parameter − There is no need to declare a single parameter in parenthesis.
Advantages of Lambda ExpressionFewer Lines of Code − One of the most benefits of a lambda expression is to reduce the amount of code. We know that lambda expressions can be used only with a functional interface. For instance, Runnable is a functional interface, so we can easily apply lambda expressions.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With