【问题标题】:How to pass a Lambda expression as a method argument in JDK8 with reflection如何在 JDK8 中通过反射将 Lambda 表达式作为方法参数传递
【发布时间】:2014-12-19 19:49:59
【问题描述】:

在 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表达式调用它吗?

【问题讨论】:

  • 您能否澄清参数类型是否仅在运行时才知道
  • 我不太确定你想问什么。请说明您要做什么以及遇到问题的原因。
  • Method m = meths[1]; // test method 不代表test(IntPredicate func) 方法。您需要使用Method m = thisC.getDeclaredMethod("test", IntPredicate.class); 之类的东西。您可以使此代码更具动态性,但为了更多地帮助您,我们需要准确了解您想要实现的目标以及在运行时您将拥有哪些数据。
  • 顺便说一句,从不忽略异常,始终处理它们,即使只是简单地打印它们的堆栈跟踪。
  • @Bohemian 然后你假设了一个稍后可能会被重构的属性。始终针对签名和隐式合约进行编码,而不是针对实现细节。

标签: java reflection lambda


【解决方案1】:

在编译时不知道目标类型的情况下,您无法创建 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 不需要是 direct 的,即不需要代表一个真实的方法。所以你可以创建一个handle invariably returning a constant 并使用dropArguments 插入额外的形式参数,直到你拥有一个可以传递给asInterfaceInstance 的具有正确功能签名的句柄

【讨论】:

  • 这适用于这个例子。谢谢。但看起来我必须包含很多方法(如上面的 lambda(int) )。我在运行时可能拥有的每个签名乘以我可能传递给方法的 lambda 的数量。不过,这种方法看起来很有希望,我会进一步探索。
【解决方案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);

【讨论】:

  • 我以 IntPredicate 为例。一般来说,我在编译时不知道接口是什么,所以我不能使用类型转换。
  • 您可能会寻找运行时代码生成。我更新了我的答案。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-12-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多