How to pass Lambda expression as method argument in JDK8 with reflection
In JDK 8, I can use reflection to call a method with a FunctionalInterface parameter passing in a Lambda expression. For example, this works.
import java.util.function.IntPredicate;
import java.lang.reflect.Method;
public class LambdaReflect {
public static void main(String args[]) {
System.out.println(test(x->true));
// Now do this in reflection
Class<LambdaReflect> thisC = LambdaReflect.class;
Method meths[] = thisC.getDeclaredMethods();
Method m = meths[1]; // test method
try {
IntPredicate lamb = x->true;
boolean result = (Boolean) m.invoke(null, lamb);
System.out.println(result);
} catch (Exception ex) {
ex.printStackTrace();
}
}
public static boolean test(IntPredicate func) {
return func.test(1);
}
}
However, if the type of the parameter is only known at runtime, how can I pass the lambda expression to the method? In other words, if at compile time I don't know the type of the method argument, but only know that it is a functional interface, can I use reflection to invoke it using a lambda expression?
source to share
You cannot create a lambda expression without knowing the type of the target at compile time. But you can put lambdas code in a method and create a method reference for that method. This is similar to how lambda expressions are compiled. The difference is that the implementation of the functional interface is created explicitly using reflective code:
import java.lang.invoke.*;
import java.util.function.IntPredicate;
import java.lang.reflect.Method;
public class LambdaReflect {
public static void main(String args[]) {
try {
for(Method m: LambdaReflect.class.getDeclaredMethods()) {
if(!m.getName().equals("test")) continue;
// we don’t know the interface at compile-time:
Class<?> funcInterface=m.getParameterTypes()[0];
// but we have to know the signature to provide implementation code:
MethodType type=MethodType.methodType(boolean.class, int.class);
MethodHandles.Lookup l=MethodHandles.lookup();
MethodHandle target=l.findStatic(LambdaReflect.class, "lambda", type);
Object lambda=LambdaMetafactory.metafactory(l, "test",
MethodType.methodType(funcInterface), type, target, type)
.getTarget().invoke();
boolean result = (Boolean) m.invoke(null, lambda);
System.out.println(result);
break;
}
} catch (Throwable ex) {
ex.printStackTrace();
}
}
private static boolean lambda(int x) { return true; }
public static boolean test(IntPredicate func) {
return func.test(1);
}
}
If you want to implement arbitrary functional signatures (which implies that the implementation is fairly trivial, independent of unknown parameters), you can use MethodHandleProxies
. The difference is that it MethodHandle
doesn't have to be straight, i.e. Doesn't have to represent a real method. Thus, you can create a descriptor that always returns a constant and use dropArguments
it to insert additional formal parameters until you have a descriptor with the right functional signature, which you can pass toasInterfaceInstance
source to share
You can always specify a lambda expression using type casting, which allows the compiler to infer a functional interface:
m.invoke(null, (IntPredicate) (x -> true));
However, if you know the signature well, why are you using reflection? If you want to create a runtime interface, you must implement the interface with a class that is generated at runtime. Have a look at Java proxy or my Byte Buddy library for this. Thus, you can specify the argument like this:
IntPredicate instance = new ByteBuddy()
.subclass(IntPredicate.class)
.method(named("test")).intercept(FixedValue.value(true));
.make()
.load(getClass().getClassLoader(), ClassLoadingStrategy.Default.WRAPPER)
.getLoadedClass()
.newInstance();
m.invoke(null, instance);
source to share