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

Gar*_*our 29 java lambda functional-programming java-8

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

下面的代码尝试遍历字符串列表并添加每个字符串的第一个字符的代码点值.似乎永远不会使用最终lambda返回的值,如果插入println,它似乎永远不会被调用.该文档将其描述为"组合器",但我无法找到更多细节......

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

Mat*_*ick 18

你在谈论这个功能吗?

reduce <U> U reduce(U identity,
             BiFunction<U,? super T,U> accumulator,
             BinaryOperator<U> combiner) 
Run Code Online (Sandbox Code Playgroud)

使用提供的标识,累积和组合功能,对此流的元素执行减少.这相当于:

 U result = identity;
 for (T element : this stream)
     result = accumulator.apply(result, element)
 return result;   
Run Code Online (Sandbox Code Playgroud)

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

 combiner.apply(u, accumulator.apply(identity, t)) == 
     accumulator.apply(u, t)   
Run Code Online (Sandbox Code Playgroud)

这是终端操作.

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

我假设它的目的是允许并行计算,所以我的猜测是它只在并行执行还原时使用.如果按顺序执行,则无需使用combiner.我肯定不知道这一点 - 我只是根据文档评论猜测"[...]不限于按顺序执行"以及评论中的"并行执行"的许多其他提及.

  • 简短的回答是并行案例需要它.(注意只有当累加器的输入和输出类型不同时才需要显式组合器;过载减少(BinaryOperator)不需要组合器.)那么,为什么不有两个版本?因为我们不区分顺序和并行流.流只有一个流时间.顺序执行最好被认为是并行执行的退化情况,需要组合器. (6认同)
  • 那似乎就是这样.插入".parallel()"会导致返回1000000. (2认同)

And*_*nin 9

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


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

<U> U reduce(U identity,
              BiFunction<U, ? super T, U> accumulator,
              BinaryOperator<U> combiner);
Run Code Online (Sandbox Code Playgroud)

这里,identity元素既是缩减的初始种子值,也是没有输入元素的默认结果.累加器函数获取部分结果和下一个元素,并产生新的部分结果.组合器功能组合了两个部分结果以产生新的部分结果.(组合器在并行缩减中是必要的,其中输入被分区,为每个分区计算部分累积,然后组合部分结果以产生最终结果.)更正式地,标识值必须是组合器的标识功能.这意味着,对所有人来说u,combiner.apply(identity, u)等于u.此外,组合功能必须是关联的,并且必须与蓄电池功能兼容:对于所有ut,combiner.apply(u, accumulator.apply(identity, t))一定要equals()accumulator.apply(u, t).

三参数形式是两参数形式的概括,将映射步骤结合到累积步骤中.我们可以使用更一般的形式重新构建简单的权重总和示例,如下所示:

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

虽然显式的map-reduce形式更具可读性,因此通常应该是首选.通过将映射和缩减组合成单个函数,可以优化远离重要工作的情况提供通用形式.


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

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

之前在同一个文档中提到了显式形式:

int sumOfWeights = widgets.parallelStream()
        .filter(b -> b.getColor() == RED)
        .mapToInt(b -> b.getWeight())
        .sum();
Run Code Online (Sandbox Code Playgroud)


小智 6

简单的测试代码来确认组合器的使用:

String[] strArray = {"abc", "mno", "xyz"};
List<String> strList = Arrays.asList(strArray);

System.out.println("stream test");
int streamResult = strList.stream().reduce(
        0, 
        (total,s) -> { System.out.println("accumulator: total[" + total + "] s[" + s + "] s.codePointAt(0)[" + s.codePointAt(0) + "]"); return total + s.codePointAt(0); }, 
        (a,b) -> { System.out.println("combiner: a[" + a + "] b[" + b + "]"); return 1000000;}
    );
System.out.println("streamResult: " + streamResult);

System.out.println("parallelStream test");
int parallelStreamResult = strList.parallelStream().reduce(
        0, 
        (total,s) -> { System.out.println("accumulator: total[" + total + "] s[" + s + "] s.codePointAt(0)[" + s.codePointAt(0) + "]"); return total + s.codePointAt(0); }, 
        (a,b) -> { System.out.println("combiner: a[" + a + "] b[" + b + "]"); return 1000000;}
    );
System.out.println("parallelStreamResult: " + parallelStreamResult);

System.out.println("parallelStream test2");
int parallelStreamResult2 = strList.parallelStream().reduce(
        0, 
        (total,s) -> { System.out.println("accumulator: total[" + total + "] s[" + s + "] s.codePointAt(0)[" + s.codePointAt(0) + "]"); return total + s.codePointAt(0); }, 
        (a,b) -> { System.out.println("combiner: a[" + a + "] b[" + b + "] a+b[" + (a+b) + "]"); return a+b;}
    );
System.out.println("parallelStreamResult2: " + parallelStreamResult2);
Run Code Online (Sandbox Code Playgroud)

输出:

stream test
accumulator: total[0] s[abc] s.codePointAt(0)[97]
accumulator: total[97] s[mno] s.codePointAt(0)[109]
accumulator: total[206] s[xyz] s.codePointAt(0)[120]
streamResult: 326
parallelStream test
accumulator: total[0] s[mno] s.codePointAt(0)[109]
accumulator: total[0] s[abc] s.codePointAt(0)[97]
accumulator: total[0] s[xyz] s.codePointAt(0)[120]
combiner: a[109] b[120]
combiner: a[97] b[1000000]
parallelStreamResult: 1000000
parallelStream test2
accumulator: total[0] s[mno] s.codePointAt(0)[109]
accumulator: total[0] s[xyz] s.codePointAt(0)[120]
accumulator: total[0] s[abc] s.codePointAt(0)[97]
combiner: a[109] b[120] a+b[229]
combiner: a[97] b[229] a+b[326]
parallelStreamResult2: 326
Run Code Online (Sandbox Code Playgroud)