【问题标题】:Java 8 Functional Programming - Passing function along with its argumentJava 8 函数式编程 - 传递函数及其参数
【发布时间】:2017-07-18 02:39:53
【问题描述】:

我有一个关于 Java 8 函数式编程的问题。我正在尝试使用函数式编程来实现某些目标,并且需要一些关于如何做到这一点的指导。

我的要求是将每个方法执行包装在计时器函数中,该函数对方法执行进行计时。这是计时器功能和我需要计时的两个功能的示例。

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 期望执行不同数量和类型的参数。

任何想法我该如何实现它。

谢谢!!

【问题讨论】:

    标签: functional-programming java-8


    【解决方案1】:

    您应该通过Separation of Concerns 将其分成两部分,以使您的代码易于使用和维护。一个是时机,另一个是调用,例如:

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

    注意事项

    &从timerMethod 中删除return 关键字。

    如果您的某些函数将引发检查异常,您可以将 Supplier 替换为 Callable 并调用 Callable#call

    【讨论】:

    • 很好的解释。谢谢!!
    • @user2751441 一点也不。当我回答你的问题时,我也在学习,:)
    【解决方案2】:

    不要保留参数,然后在最后一刻传递它们。立即传递它们,但通过用另一个函数包装它来延迟调用该函数:

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

    在这里,我将每个函数调用包装在一个接受 0 个参数的 lambda (() -&gt;...) 中。然后,稍后再调用它们,不带参数:

    f1() 
    f2()
    

    这对您在 lambda 中提供的参数形成了一个闭包,它允许您稍后使用这些变量,即使通常它们会因为超出范围而被 GC。

    注意,我有一个? 作为Producer 的类型,因为我不知道你的函数返回什么类型。将? 更改为每个函数的返回类型。

    【讨论】:

      【解决方案3】:

      简介

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

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

      如果您以抽象的方式思考它,则不存在诸如具有多个参数的函数之类的东西。函数要么接收一组值(又名 tuple),要么接收一个参数并返回另一个接收另一个参数的函数,该函数又返回另一个返回...等等,序列的最后一个函数返回一个实际结果(又名 currying)。

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


      方法 #1:元组

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

      因此,您的示例中的 functionA 可能会收到 一个 Tuple2&lt;String, String&gt; 作为参数:

      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,在调用时将参数现在传递给它:

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

      您可以对示例中的functionB 采取类似的方法,但您需要使用Tuple3&lt;Integer, Integer, String[]&gt; 作为参数(注意可变参数)。

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


      方法 #2:柯里化

      另一种方法是使用一种称为currying 的技术,即接受一个参数的函数并返回另一个接受另一个参数的函数等,序列的最后一个函数返回实际结果。

      以下是为您的 2 参数方法 functionA 创建 currified 函数的方法:

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

      按如下方式调用它:

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

      如果您想装饰 currifiedFunctionA 使用上面定义的timerMethod 方法,您可以执行以下操作:

      Function<String, Function<String, SomeReturnType>> timedCurrifiedFunctionA =
          arg1 -> timerMethod("timerCurryA", arg2 -> functionA(arg1, arg2));
      

      然后,调用 timedCurrifiedFunctionA,就像使用任何 currified 函数一样:

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

      请注意,您只需要装饰序列的最后一个函数,即实际调用方法的函数,这是我们要测量的。

      对于您的示例中的方法functionB,您可以采用类似的方法,只是currified 函数的类型现在是:

      Function<Integer, Function<Integer, Function<String[], SomeResultType>>>
      

      至少可以这么说,这很麻烦。所以这是 Java 中 currified 函数的缺点:表达它们类型的语法。另一方面,currified 函数使用起来非常方便,允许您应用多种函数式编程技术,而无需编写辅助类。

      【讨论】:

      • 答案已经关闭。 :(。你写的更详细。up!!
      猜你喜欢
      • 2020-10-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-07-01
      • 1970-01-01
      • 2018-04-13
      • 1970-01-01
      • 2016-02-24
      相关资源
      最近更新 更多