如何使用反射将Lambda表达式作为JDK8中的方法参数传递

时间:2014-12-19 19:50:00

标签: java reflection lambda

在JDK 8中,我可以使用反射来调用具有FunctionalInterface参数的方法,并传递Lambda表达式。例如,这有效。

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

但是,如果参数类型仅在运行时已知,那么如何将lambda表达式传递给方法?换句话说,如果我在编译时不知道方法的参数类型,但只知道它是一个函数接口,我可以使用反射来用lambda表达式调用它吗?

2 个答案:

答案 0 :(得分:4)

如果在编译时不知道目标类型,则无法创建lambda表达式。但是您可以将lambda的代码放入方法中并创建对此方法的方法引用。这类似于编译lambda表达式的方式。不同之处在于使用反射代码明确创建了功能接口实现:

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

如果要实现任意功能签名(这意味着实现相当简单,不依赖于未知参数),您可以使用MethodHandleProxies。区别在于MethodHandle不需要直接,即不需要代表真实的方法。因此,您可以创建handle invariably returning a constant并使用dropArguments插入其他形式参数,直到您拥有一个具有正确功能签名的句柄,您可以将其传递给asInterfaceInstance

答案 1 :(得分:2)

您始终可以使用类型转换来指示lambda表达式,该类型转换允许编译器推断功能接口:

m.invoke(null, (IntPredicate) (x -> true));

但是,如果您知道签名那么好,为什么要使用反射?如果要生成接口的运行时实现,则应使用运行时生成的类实现该接口。查看Java代理或我的库Byte Buddy。这样,您可以按如下方式提供参数:

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