Java 8函数式编程-传递带有参数的函数

3

我有一个关于Java 8函数式编程的问题。我想要使用函数式编程来实现一些内容,并需要一些指导。

我的需求是将每个方法执行包装在计时器函数中,以计算方法执行时间。这里是计时器函数和我需要计时的2个函数的示例:

timerMethod(String timerName, Function func){
  timer.start(timerName)
  func.apply()
  timer.stop()
}

functionA(String arg1, String arg2)

functionB(int arg1, intArg2, String ...arg3)

我想把 functionAfunctionB 传递给 timerMethod,但是 functionAfunctionB 要求执行时具有不同数量和类型的参数。

你有什么好的想法吗?

谢谢!!

3个回答

4

为使你的代码易于使用和维护,你应该通过关注点分离原则将其分成两个部分。一个是定时,另一个是调用,例如:

//                                       v--- invoking occurs in request-time
R1 result1 = timerMethod("functionA", () -> functionA("foo", "bar"));
R2 result2 = timerMethod("functionB", () -> functionB(1, 2, "foo", "bar"));


// the timerMethod only calculate the timing-cost
<T> T timerMethod(String timerName, Supplier<T> func) {
    timer.start(timerName);
    try {
        return func.get();
    } finally {
        timer.stop();
    }
}

如果你想返回一个函数接口而不是该方法的结果,你可以按照以下方式完成:

Supplier<R1> timingFunctionA =timerMethod("A", ()-> functionA("foo", "bar"));
Supplier<R2> timingFunctionB =timerMethod("B", ()-> functionB(1, 2, "foo", "bar"));


<T> Supplier<T> timerMethod(String timerName, Supplier<T> func) {
    //      v--- calculate the timing-cost when the wrapper function is invoked
    return () -> {
        timer.start(timerName);
        try {
            return func.get();
        } finally {
            timer.stop();
        }
    };
}

注意事项

如果你所有函数的返回值类型都是void,那么你可以将Supplier替换为Runnable,并将timerMethod的返回类型改为void,同时从timerMethod中删除return关键字,即可。

如果你的一些函数会抛出已检查异常,那么你可以将Supplier替换为Callable,并调用Callable#call方法。


很好的解释。谢谢!! - user2751441
@user2751441 没有问题。当我回答你的问题时,我也在学习 :) - holi-java

1
不要在最后一刻保留参数并传递它们。立即传递它们,但通过将其包装在另一个函数中延迟调用函数:
Producer<?> f1 =
    () -> functionA(arg1, arg2);

Producer<?> f2 =
    () -> functionB(arg1, arg2, arg3);

在这里,我将每个函数调用都包装在一个不需要参数的lambda表达式(() ->...)中。然后,稍后只需不带参数调用它们即可。
f1() 
f2()

这形成了对你在lambda中提供的参数的闭包,这允许你稍后使用这些变量,即使通常它们已经因超出范围而被GC回收。
请注意,我将?作为Producer类型,因为我不知道你的函数返回什么类型。将?更改为每个函数的返回类型。

1

介绍

其他答案展示了如何使用闭包来捕获函数的参数,无论其数量。这是一种很好的方法,非常有用,如果您事先知道参数,以便可以捕获它们

在这里,我想展示另外两种方法,不需要您事先知道参数...

如果您从抽象的角度考虑,不存在具有多个参数的函数。函数要么接收一个值集合(也称为元组),要么接收一个单一参数并返回另一个接收另一个单一参数的函数,然后返回另一个一参数函数,以此类推,序列的最后一个函数返回实际结果(也称为柯里化)。

Java中的方法可能具有多个参数。因此,挑战在于构建始终只接收一个参数的函数(通过元组或柯里化),但实际上调用接收多个参数的方法。


方法一:元组

第一种方法是使用 Tuple 辅助类,并使您的函数接收一个元组,可以是 Tuple2Tuple3

因此,您示例中的 functionA 可能会接收 一个单独的 Tuple2<String, String> 作为参数:

Function<Tuple2<String, String>, SomeReturnType> functionA = tuple -> 
    functionA(tuple.getFirst(), tuple.getSecond());

你可以按如下方式调用它:

SomeReturnType resultA = functionA.apply(Tuple2.of("a", "b"));

现在,为了使用你的timerMethod方法装饰functionA函数,你需要进行一些修改:
static <T, R> Function<T, R> timerMethod(
        String timerName, 
        Function<? super T, ? extends R> func){
    return t -> {
        timer.start(timerName);
        R result = func.apply(t);
        timer.stop();
        return result;
    };
}

请注意,您应该使用try/finally块使您的代码更加健壮,如holi-java's answer所示。
以下是您可能如何使用timerMethod方法来执行functionA:
Function<Tuple2<String, String>, SomeReturnType> timedFunctionA = timerMethod(
    "timerA", 
    tuple -> functionA(tuple.getFirst(), tuple.getSecond());

您可以像调用其他函数一样调用timedFunctionA,在调用时传递参数now

SomeReturnType resultA = timedFunctionA.apply(Tuple2.of("a", "b"));

您可以采用类似的方法来处理示例中的functionB,只是需要使用Tuple3<Integer, Integer, String[]>作为参数(处理可变参数)。

这种方法的缺点是需要创建许多Tuple类,例如Tuple2Tuple3Tuple4等,因为Java缺乏对元组的内置支持。


方法二:柯里化

另一种方法是使用一种称为柯里化的技术,即接受一个单一参数并返回另一个接受另一个单一参数的函数,以此类推,序列中的最后一个函数返回实际结果。

以下是如何为您的双参数方法functionA创建柯里化函数:

Function<String, Function<String, SomeReturnType>> currifiedFunctionA =
    arg1 -> arg2 -> functionA(arg1, arg2);

按以下方式调用:

SomeReturnType result = currifiedFunctionA.apply("a").apply("b");

如果你想使用上面定义的timerMethod方法装饰currifiedFunctionA,可以按照以下步骤进行:
Function<String, Function<String, SomeReturnType>> timedCurrifiedFunctionA =
    arg1 -> timerMethod("timerCurryA", arg2 -> functionA(arg1, arg2));

然后,正如对任何柯里化函数一样,调用timedCurrifiedFunctionA

SomeReturnType result = timedCurrifiedFunctionA.apply("a").apply("b");

请注意,您只需要装饰序列的最后一个函数,即实际调用方法的函数,这是我们想要测量的内容。
对于您示例中的functionB方法,您可以采用类似的方法,但现在柯里化函数的类型将是:
Function<Integer, Function<Integer, Function<String[], SomeResultType>>>

可以说,Java中柯里化函数的缺点在于其类型表达式语法相当繁琐。但另一方面,柯里化函数非常实用,可以应用多种函数式编程技术而无需编写辅助类。


1
答案已经关闭了。 :( 请您写得更加详细一些,加油! - holi-java

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接