有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

Java8函数组合和

我发现我的Java知识在Java8中已经过时了,我正在尝试学习许多新的语言特性。其中一个是函数,特别是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方法的存在是有原因的,函数或双函数可能并不总是以这种方式可逆的。你能想到可逆性不适用的情况吗


共 (6) 个答案

  1. # 1 楼答案

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

    Returns a composed function that first applies the before function to its input, and then applies this function to the result.

    Returns a composed function that first applies this function to its input, and then applies the after function to the result.

    因此,可逆性将取决于功能的实现

    在您的例子中,itowitow2只是表达相同内容的两种可选方式:“按此顺序运行itos,然后运行stow

  2. # 2 楼答案

    下面的示例显示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
        }
    }
    
  3. # 3 楼答案

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

    因此,考虑到它们是作为一种便利而存在的,它们在不同的情况下都是方便的。您可以选择在特定情况下最具表现力的选项

  4. # 4 楼答案

    虽然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表达式编写

  5. # 5 楼答案

    它们是等价的

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

  6. # 6 楼答案

    我认为它们是等效的,请参见实现:

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