Java 8 函数式编程中“reduce”函数的第三个参数的目的

在什么情况下,Java 8流中“reduce”的第三个参数被调用?

下面的代码尝试遍历字符串列表,并将每个字符串的第一个字符的码位值相加。最终 lambda 返回的值似乎从未被使用过,如果插入 println,它似乎永远不会被调用。文档将其描述为“组合器”,但我找不到更多详细信息...

int result =
  data.stream().reduce(0, (total,s) -> total + s.codePointAt(0), (a,b) -> 1000000); 

答案 1

你说的是这个功能吗

reduce <U> U reduce(U identity,
             BiFunction<U,? super T,U> accumulator,
             BinaryOperator<U> combiner) 

使用提供的标识、累积和组合函数对此流的元素执行缩减。这等效于:

 U result = identity;
 for (T element : this stream)
     result = accumulator.apply(result, element)
 return result;   

但不限于按顺序执行。标识值必须是组合器函数的标识。这意味着对于所有 u,组合器(identity, u) 等于你。此外,合路器功能必须与累加器功能兼容;对于所有 u 和 t,以下必须成立:

 combiner.apply(u, accumulator.apply(identity, t)) == 
     accumulator.apply(u, t)   

这是一个终端操作。

API 注意:使用此表单的许多简化可以通过映射和 reduce 操作的显式组合来更简单地表示。累加器函数充当融合映射器和累加器,这有时比单独的映射和归约更有效,例如,当知道先前减小的值允许您避免一些计算时。类型参数:U - 结果的类型 参数:标识 - 合路器函数累加器的标识值 - 用于将附加元素合并到结果合路器中的关联,非干扰,无状态函数 - 用于组合两个值的关联,非干扰,无状态函数,必须与累加器函数兼容 返回:约简结果 另请参阅: reduce(BinaryOperator), reduce(Object, BinaryOperator)

我假设它的目的是允许并行计算,所以我的猜测是,只有当并行执行简化时,它才会被使用。如果按顺序执行,则无需使用 。我不确定这一点 - 我只是根据文档评论“[...]不限于按顺序执行“,注释中多次提到”并行执行”。combiner


答案 2

我认为软件包摘要中的减少操作段落可以回答这个问题。让我在这里引用最重要的部分:java.util.stream


在其更一般的形式中,对生成类型结果的类型元素的 reduce 运算需要三个参数:<T><U>

<U> U reduce(U identity,
              BiFunction<U, ? super T, U> accumulator,
              BinaryOperator<U> combiner);

此处,标识元素既是缩减的初始种子值,也是默认结果(如果没有输入元素)。累加器函数采用部分结果和下一个元素,并生成新的部分结果。合并器函数将两个部分结果组合在一起以生成新的部分结果。(在并行约简中,合并器是必需的,其中输入被分区,为每个分区计算部分累积,然后将部分结果组合以产生最终结果。更正式地说,标识值必须是组合器函数的标识。这意味着对于所有 ,等于 。此外,合路器函数必须是结合的,并且必须与累加器函数兼容:对于 all 和 ,必须为 。ucombiner.apply(identity, u)uutcombiner.apply(u, accumulator.apply(identity, t))equals()accumulator.apply(u, t)

三参数形式是两个参数形式的推广,将映射步骤合并到累积步骤中。我们可以使用更通用的形式重新转换简单的权重和示例,如下所示:

 int sumOfWeights = widgets.stream()
                           .reduce(0,
                                   (sum, b) -> sum + b.getWeight())
                                   Integer::sum);

尽管显式 map-reduce 形式更具可读性,因此通常应该是首选。广义形式适用于可以通过组合映射和简化为单个函数来优化重要工作的情况。


换句话说,据我所知,三参数形式在两种情况下是有用的:

  1. 当并行执行很重要时。
  2. 当可以通过组合映射和累积步骤来实现显着的性能优化时。否则,可以使用更简单和可读的显式 map-reduce 表单。

显式表单在同一文档中前面提到过:

int sumOfWeights = widgets.parallelStream()
        .filter(b -> b.getColor() == RED)
        .mapToInt(b -> b.getWeight())
        .sum();