【问题标题】:Java 8 Functions - compose and andThenJava 8 函数 - 组合和然后
【发布时间】:2017-10-06 12:57:24
【问题描述】:

我发现我的 Java 知识在 Java 8 中已经过时,并且我正在尝试学习许多新的语言特性。其中之一是函数,特别是 composeandThen 方法。

我写了一个小实验来测试composeandThen的可逆性:

/** Wraps a value 
 */
public class Wrap<T> {
    private final T value;
    private Wrap(T value) {
        this.value = value;
    }
    public static <T> Wrap<T> of(T value) {
        return new Wrap<>(value);
    }
}

static void functions() {

    Function<Integer,String> itos = i->"'"+i+"'";
    Function<String,Wrap<String>> stow = s->Wrap.of(s);

    Function<Integer,Wrap<String>> itow = itos.andThen(stow);
    Function<Integer,Wrap<String>> itow2 = stow.compose(itos);

    System.out.println(itow.apply(3));
    System.out.println(itow2.apply(3));
}

在上面的代码中,正如预期的那样,itowitow2 两个函数似乎是等价的。但它们实际上是等价的吗?在这种情况下是否有相同的结果?

认为composeandThen 方法的存在是有原因的,并且函数或BiFunction 可能并不总是以这种方式可逆。你能想到可逆性不适用的任何情况吗?

【问题讨论】:

  • 进一步思考,我对 BiFunction 感到困惑,当然他们没有 compose() 原因很明显。看起来函数实际上根本不需要 compose(),andThen() 在 IMO 上更直观。

标签: java function functional-programming


【解决方案1】:

虽然a.andThen(b) 等同于b.compose(a),但在使用方面存在实际差异,当这两个函数中只有一个是已经存在的函数时。

假设,你有已经存在的功能

Function<Integer, String> iToHex = i -> "'" + Integer.toHexString(i) + "'";

然后,你想链接一个字符串转换,例如

Function<Integer, String> itoUpperHex = iToHex.andThen(String::toUpperCase);

andThen 显然比

方便多了
Function<Integer,String> itoUpperHex
                       = ((Function<String,String>)String::toUpperCase).compose(iToHex);

另一方面,如果你已经存在的功能是

Function<String, String> quote = s -> "'" + s + "'";

并且你想创建引用的十六进制函数,

Function<Integer, String> iToHex = quote.compose(Integer::toHexString);

相比

会方便很多
Function<Integer, String> iToHex = ((Function<Integer, String>) Integer::toHexString).andThen(quote);

因此,选择哪种方法取决于哪个函数已经存在。如果两者都是现有函数,则使用哪种方法并不重要(除非您怀疑其中一个函数可能已覆盖这些方法以执行更有效的组合)。如果您没有现有函数作为起点,则没有理由使用这些方法中的任何一种,因为您可以将组合表达式编写为单个 lambda 表达式。

【讨论】:

    【解决方案2】:

    它们是等价的。

    或者换句话说:x.andThen(y)y.compose(x) 相同。

    【讨论】:

    • 是的,但为什么要引入 2 种方法?
    • 它们给出相同的输出,但它们的工作顺序不同,请参阅我的回答
    【解决方案3】:

    从 Javadocs 来看,composeandThen 之间有明显的区别:

    返回一个组合函数,该函数首先将 before 函数应用于其输入,然后将此函数应用于结果。

    返回一个组合函数,该函数首先将此函数应用于其输入,然后将 after 函数应用于结果。

    因此,可逆性将取决于您的函数的实现。

    在您的情况下,itowitow2 只是表达相同的两种替代方式:“按此顺序运行 itos,然后运行 ​​stow”。

    【讨论】:

    • 可逆性如何取决于我的函数的实现?当然这两个函数是否可以以这种方式链接仅取决于一个的结果类型和下一个的输入类型?
    • @NickJ 我只是说一般来说,即某些转换可能不可逆,即使无论输入/输出类型如何。当然,这不是你的情况。
    • 那你为什么要创建两种方法呢?为什么不直接将 x.andThen(y) 替换为 y.andThen(x) ?
    【解决方案4】:

    Function 的源代码免费提供:

    default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
        Objects.requireNonNull(before);
        return (V v) -> apply(before.apply(v));
    }
    
    default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
        Objects.requireNonNull(after);
        return (T t) -> after.apply(apply(t));
    }
    

    由此可见a.andThen(b) 等价于b.compose(a)

    如果我们将隐含的this 明确表示出来,可能会更清楚:

    return (V v) -> this .apply(before.apply(v)); //compose
    return (T t) -> after.apply(this  .apply(t)); // andThen
    

    你可能会问,如果它们是等价的,为什么两者都存在?

    嗯,它们首先只是为了方便。您可以在没有它们的情况下进行管理:

     a.andThen(b)
    

    ...当然等价于:

     (x) -> b.apply(a.apply(x));
    

    因此,鉴于它们的存在是为了方便,因此每种方法在不同的情况下都很方便。您可以选择在特定情况下最具表现力的选项。

    【讨论】:

      【解决方案5】:

      我会说它们是等价的,请参阅实现:

      x.compose(y) = x.apply(y.apply(...))

      y.andThen(x) = x.apply(y.apply(...))

      来自Function.java(为了清楚起见,我添加了this.):

      default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
          Objects.requireNonNull(before);
          return (V v) -> this.apply(before.apply(v));
      }
      
      default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
          Objects.requireNonNull(after);
          return (T t) -> after.apply(this.apply(t));
      }
      

      【讨论】:

        【解决方案6】:

        下面的例子表明andThen()compose() 是相互矛盾的方法。它会产生相反的结果。(有关更多说明,请参阅输出)

        f1.andThen(f2).apply(10); => 首先执行f1.apply(10) 方法。基于输出第一方法,f2.apply(result_of_f1_method)方法执行。

        f1.compose(f2).apply(10); => 这里与andThen() 方法正好相反。 首先f2.apply() 然后f1.apply(output_of_f2_method) 执行。

        public class FunctionDemo {
            public static void main(String[] args) {
        
                Function<Integer, Integer> f1 = num -> (num - 4);
                Function<Integer, Integer> f2 = num -> (num * 2);
        
                // Using andThen() method
                int a=f1.andThen(f2).apply(10);
                System.out.println(a);// Output - 12
        
                //Using compose function
                int b=f1.compose(f2).apply(10);
                System.out.println(b);// Output - 16
            }
        }
        

        【讨论】:

          【解决方案7】:

          它们的工作顺序不同。

          一个简单的例子 -

          方案一:

          Function<String, String> x = myString -> myString + ",\tx function";
          Function<String, String> y = myString -> myString + ",\ty function";
          
          x = x.andThen(y);
          
          System.out.println(x.apply("Hello"));
          

          输出 -

          Hello,  x function, y function
          

          在这里,x function 函数被执行然后 y function 被执行

          方案二:

          Function<String, String> x = myString -> myString + ",\tx function";
          Function<String, String> y = myString -> myString + ",\ty function";
          
          y = y.compose(x);
          
          System.out.println(y.apply("Hello"));
          

          输出 -

          Hello,  x function, y function
          

          在这里,y function 组合 x function 首先然后执行自己。

          因此,虽然结果相同,但执行的顺序不同。

          第二个例子-

                  final double startNumber = 3.5;
          
                  final Function<Double, Double> cubeRoot = x -> {
                      System.out.println("Calculating cube root");
                      return Math.pow(x, 1.0/3);
                  };
          
                  final Function<Double, Double> square = x -> {
                      System.out.println("Calculating square");
                      return Math.pow(x, 2);
                  };
          
                  final Function<Double, Long> round = x -> {
                      System.out.println("Rounding");
                      return Math.round(x);
                  };
          
                  Long result = cubeRoot.andThen(square).andThen(round).apply(startNumber);
                  System.out.println("Result: " + result);
          
                  result = round.compose(square).compose(cubeRoot).apply(startNumber);
                  System.out.println("Result: " + result);
          

          输出 -

          Calculating cube root
          Calculating square
          Rounding
          Result: 2
          Calculating cube root
          Calculating square
          Rounding
          Result: 2
          

          cubeRoot.andThen(square).andThen(round).apply(startNumber) 中,首先cubeRoot 完成,然后square 完成,然后round 完成。

          round.compose(square).compose(cubeRoot).apply(startNumber)中,首先编写并执行cubeRoot,然后编写并执行square,然后编写并执行round

          因此,虽然这里的结果也一样,但执行的顺序是不同的。

          【讨论】:

            【解决方案8】:

            除了执行功能的顺序外,两者都相似,请参阅下面的示例-

            import java.util.function.Function;
            
            public class FunctionThenComposeTest {
            
                public static void main(String[] xavis_args) {
            
                    Function<Integer, Integer> f1 = i -> {
                        System.out.println("Inside F1");
                        return i * i;
                    };
            
                    Function<Integer, Integer> f2 = i -> {
                        System.out.println("Inside F2");
                        return i * i;
                    };
            
                    System.out.println("And Then method");
                    System.out.println(f1.andThen(f2).apply(2));// Execute f1 function first, take the result of f1 and then apply it to f2 function(The result of f1 function will be the input to f2 function)
                    System.out.println("Compose method");        
                    System.out.println(f1.compose(f2).apply(2));// First execute f2 function and then take the result of f2 and execute f1 (The result of f2 will be the input to f1 function)
                }
            }
            

            输出

            And Then method
            
            Inside F1
            
            Inside F2
            
            16
            
            Compose method
            
            Inside F2
            
            Inside F1
            
            16
            

            【讨论】:

            • 提供最佳答案
            猜你喜欢
            • 2018-07-01
            • 1970-01-01
            • 2019-04-29
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多