提问者:小点点

Stream. duce(标识、累加器、组合器)如何工作?


Stream. duce有3个方法重载。

reduce(BinaryOperator<T> accumulator)
reduce(T identity, BinaryOperator<T> accumulator)
reduce(U identity, BiFunction<U,? super T,U> accumulator, BinaryOperator<U> combiner)
  • 例如,第一个重载可用于计算整数列表的总和。
  • 第二个重载是相同的,但如果列表为空,它只返回默认值。

我很难理解第三重载(Stream. duce(标识、累加器、组合器))是如何工作的,以及它的用例是什么。那么,它是如何工作的,为什么会存在呢?


共3个答案

匿名用户

如果我理解正确,您的问题是关于第三个参数组合器

首先,Java的目标之一是为顺序流和并行流提供类似的API。duce的3参数版本对并行流很有用。

假设您从Collection的值减少

匿名用户

基本上,它将映射函数与约简结合在一起。我看到的大多数示例都没有真正说明为什么在不同的步骤中调用map()和普通的duce()更可取。API注释在这里派上了用场:

使用这种形式的许多约简可以通过mapduce操作的显式组合来更简单地表示。累加器函数充当融合映射器和累加器,有时比单独的映射和约简更有效,例如当知道之前的约简值时可以让您避免一些计算。

假设我们有一个

BigDecimal product = numbers.map(BigDecimal::new)
        .reduce(BigDecimal.ONE, BigDecimal::multiply);

但这是低效的。如果其中一个数字是“0”,我们将余数转换为BigDecimal是在浪费周期。我们可以在这里使用3-argduce()来绕过映射逻辑:

BigDecimal product = numbers.reduce(BigDecimal.ONE,
        (d, n) -> d.equals(BigDecimal.ZERO) ? BigDecimal.ZERO : new BigDecimal(n).multiply(d),
        BigDecimal::multiply);

当然,完全短路流会更有效,但在流中这很棘手,尤其是在并行情况下。这只是一个让这个概念得到理解的例子。

匿名用户

注意:一些示例是为演示而设计的。在某些情况下,可以使用简单的. sum()

imo,最大的区别在于第三种形式有一个BiFunction作为第二个参数,而不是BinaryOperator。因此您可以使用第三种形式来更改结果类型。它还有一个BinaryOperator作为组合器,以组合来自并行操作的不同结果。

生成一些数据

record Data(String name, int value) {}

Random r = new Random();
List<Data> dataList = r.ints(1000, 1, 20).mapToObj(i->new Data("Item"+i, i)).toList();

没有并行操作,但类型不同。但需要第三个参数,因此只需返回总和。

int sum = dataList.stream().reduce(0, (item, data) -> item + data.value,
        (finalSum, partialSum) -> finalSum);
System.out.println(sum);

指纹

10162

第二种形式。使用map获取要求和的值。BinaryOperator这里使用,因为类型是相同的,没有并行操作。

sum = dataList.stream().map(Data::value).reduce(0, (sum1,val)->sum1+val);
System.out.println(sum); // print same as above

这与上面显示的相同,但是并行的。第三个参数累加部分和。这些总和在下一个线程完成时累加,因此输出可能没有合理的顺序。

sum = dataList.parallelStream().reduce(0, (sum1, data) -> sum1 + data.value,
        (finalSum, partialSum) -> {
           
            System.out.println("Adding " + partialSum + " to " + finalSum);
            finalSum += partialSum;
            return finalSum;
        });
System.out.println(sum);

打印如下内容

Adding 586 to 670
Adding 567 to 553
Adding 1256 to 1120
Adding 715 to 620
Adding 624 to 601
Adding 1335 to 1225
Adding 2560 to 2376
Adding 662 to 579
Adding 706 to 715
Adding 1421 to 1241
Adding 713 to 689
Adding 576 to 586
Adding 1402 to 1162
Adding 2662 to 2564
Adding 4936 to 5226
10162

最后一个注意事项。Collectors.减少方法都没有BiFunction来处理不同的类型。为了处理这个问题,第二个参数是一个Function来充当映射器,因此第三个参数,一个BinaryOperator可以收集值。

sum = dataList.parallelStream().collect(
       Collectors.reducing(0, Data::value, (finalSum, partialSum) -> {
           System.out.println(
                   "Adding " + partialSum + " to " + finalSum);      
           return finalSum + partialSum;
       }));

System.out.println(sum);