Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to pass a Lambda expression as a method argument in JDK8 with reflection

In JDK 8, I can use reflection to call a method with a FunctionalInterface parameter, passing a Lambda expression. For instance, 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 parameter type is only known at runtime, how can I pass a lambda expression to the method? In other words, if I don't know at compile time the argument type of the method, but only know that it is a functional interface, can I use reflection to call it with a lambda expression?

like image 685
Kin-man Chung Avatar asked Mar 18 '23 13:03

Kin-man Chung


1 Answers

You can’t create a lambda expression without knowing the target type at compile-time. But you can put the lambda’s code into a method and create a method reference to this method. This is similar to how lambda expressions are compiled. The difference is that the functional interface implementation 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 rather trivial, not depending on the unknown parameters), you can use MethodHandleProxies. The difference is that the MethodHandle doesn’t need to be direct then, i.e. doesn’t need to represent a real method. So you can create a handle invariably returning a constant and use dropArguments to insert additional formal parameters until you have a handle with the right functional signature which you can pass to asInterfaceInstance

like image 72
Holger Avatar answered Apr 06 '23 04:04

Holger