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?

+3


source to share


2 answers


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

+4


source


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);

      

+2


source







All Articles