有 Java 编程相关的问题?

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

如何在Java8中组合函数<T,R>和消费者<R>?

为什么我们不能像编写函数一样编写函数和消费者呢

Function<Integer, String> a = Object::toString;
Consumer<String> b = x -> System.out.println(x);
Consumer<Integer> composed = a.andThen(b);

这似乎是对现有功能界面的一种明显的即兴创作。有什么原因可以解释为什么在Java8中避免使用这种功能

此外,使用如下函数的扩展实现是否是一种常见做法

interface FunctionImproved<T, R> extends Function<T, R> {
    default Consumer<T> andThen(Consumer<R> consumer) {
        return x -> consumer.accept(apply(x));
    }
}

共 (4) 个答案

  1. # 1 楼答案

    Function<A0, A1>实例上的andThen方法需要Function<A1, A2>,而不是Consumer<A1>

    a.andThen(string -> {
        b.accept(string);
        return string;
    });
    

    它有一个副作用,因为我们的“消费者”(实际上,它是一个Function<A1, A1>)将从上一个Function<Integer, String> a函数返回一个被忽略的值。这不是我们想要的


    有一种不同的方式来组成Function<A0, A1>Consumer<A1>

    Consumer<Integer> c = i -> b.accept(a.apply(i));
    

    这可以概括为一种实用方法:

    <A0, A1> Consumer<A0> functionAndThenConsumer(Function<A0, A1> f, Consumer<A1> c) {
        return i -> c.accept(f.apply(i));
    }
    

    Is it a common practice to use an extended implementation of Function?

    有两种选择:

    1. 拥有在函数接口之间进行一些转换的静态方法(就像我做的那样)

    2. 使用默认方法扩展这些标准功能接口(就像您所做的那样)。只要选择一个正确的有意义的名字,而不是像FunctionImproved

  2. # 2 楼答案

    Why can't we compose Function and Consumer just like we compose functions?

    这都是关于Java的类型系统。Function.andThen方法接受类型为Function(不是类型为Consumer)的参数。Java中的ConsumerFunction之间没有关系,即一个不扩展另一个,因此不能将Consumer传递给FunctionandThen方法

    不过,也有解决办法。一种是使用默认方法,如您所示,而另一种方法是使用静态方法,如Andrew's answer所示

    另一种方法是使用高阶函数:

    BiFunction<Function<T, R>, Consumer<R>, Consumer<T>> composer =
        (function, consumer) -> t -> consumer.accept(function.apply(t));
    

    你可以这样使用它:

    Consumer<Integer> composed = composer.apply(Integer::toString, System.out::println);
    
  3. # 3 楼答案

    FunctionConsumer服务于两个不同的主人(在某种意义上)

    • 函数接受一种类型(在BiFunction的情况下是两种),并生成第三种类型,可以是第一种类型,也可以是第二种类型
    • 消费者接受一种(对于BiConsumer类型,则接受两种),并产生输出

    两种情况下,都应该执行离散的单一的操作,这将更容易推断操作的线程安全性

    它看起来像是在尝试将一种类型映射到另一种类型(字符串到整数),然后对其调用一个操作。如果您的元素位于Stream中,可以这样写:

    elements.map(Objects::toString).forEach(System.out::println);
    

    。。。如果它在一个集合或IntStream中,你可以用这个来代替:

    elements.forEach(System.out::println);
    

    最终,你所尝试的可能是寻找问题的解决方案。一旦你对功能和消费者实际扮演的角色有了更清晰的认识,尝试像上面这样的组合就变得不那么必要了

  4. # 4 楼答案

    没有理由不将此组合作为函数API的一部分存在。Java8的设计者根本没有预见到所有可能的组合和用例:这就是为什么Java9添加了一些更方便的方法,我们可能会在即将发布的版本中看到更多

    关于“改进”功能,我不敢仅仅为此创建一个新接口,尤其是考虑到接口名称的选择。后缀“Improved”和所有“util”、“Helpers”等一样,不具备自我解释的语义,应该避免使用。更好的解决方案是创建一个功能名称空间,如下所示:

    final class Compositions {
         private Compositions() {}
         public static <T,R> Function<Consumer<R>, Consumer<T>> to(Function<T,R> f) { 
              return consumer -> (value -> consumer.accept(f.apply(value));
         } 
    }
    
    import static Compositions.*;
    Function<Integer,String> stringValue = Integer::toString;
    Consumer<Integer> printInt = to(stringValue).apply(System.out::println);
    printInt.accept(1);