Java 8 reduce()方法在Java8中是如何工作的?

Java 8 reduce()方法在Java8中是如何工作的?,java-8,java,java-stream,reduce,Java 8,Java,Java Stream,Reduce,我试图理解reduce()方法是如何工作的 例如,我有以下代码: public class App { public static void main(String[] args) { String[] arr = {"lorem", "ipsum", "sit", "amet"}; List<String> strs = Arrays.asList(arr); int ijk = strs.stream().reduce(

我试图理解
reduce()
方法是如何工作的

例如,我有以下代码:

public class App {

    public static void main(String[] args) {
        String[] arr = {"lorem", "ipsum", "sit", "amet"};
        List<String> strs = Arrays.asList(arr);

        int ijk = strs.stream().reduce(0, 
            (a, b) -> { 
                System.out.println("Accumulator, a = " + a + ", b = " + b);
                return a + b.length();
            },
            (a, b) -> {
                System.out.println("Combiner");
                return a * b;
            });
        System.out.println(ijk); 
    }
}
它是这些字符串长度的总和。我看到合并器没有被访问,所以它不会对数字进行乘法,它只对数字进行加法

但是如果我将
替换为
并行流

int ijk = strs.parallelStream().reduce(0, 
    (a, b) -> { 
        System.out.println("Accumulator, a = " + a + ", b = " + b);
        return a + b.length();
    },
    (a, b) -> {
        System.out.println("Combiner");
        return a * b;
    });

System.out.println(ijk); 
这是输出:

Accumulator, a = 0, b = ipsum
Accumulator, a = 0, b = lorem
Accumulator, a = 0, b = sit
Combiner
Accumulator, a = 0, b = amet
Combiner
Combiner
300

我看到累加器和合并器都被访问,但只有乘法返回。那么求和会发生什么呢?

我假设您选择做加法和乘法只是为了演示一下到底发生了什么

正如您已经注意到的,正如前面提到的,合并器只在并行流上被调用

简而言之,在并行STRAM上,流的一部分(即底层拆分器)被切断,并由不同的线程处理。在对几个部分进行处理之后,它们的结果将与组合器合并

在您的例子中,这四个元素都由不同的线程处理,然后按元素进行组合。这就是为什么你看不到任何加法(除了
0+
)被应用,而只看到乘法


但是,为了获得有意义的结果,您应该从
*
切换到
+
,而不是执行更有意义的输出。

您应该阅读
reduce
的文档,其中说明:

此外,组合器功能必须与累加器功能兼容;对于所有u和t,必须满足以下条件:

组合器.apply(u,累加器.apply(identity,t))==累加器.apply(u,t)

在您的例子中,您违反了这条定律(在
累加器中进行求和,在
组合器中进行乘法),因此您看到的这种操作的结果实际上是未定义的,并且取决于底层源的拆分器是如何实现的(不要这样做!)


此外,
组合器
仅用于并行流

当然,您的整个方法可以简化为:

Arrays.asList("lorem", "ipsum", "sit", "amet")
      .stream()
      .mapToInt(String::length)
      .sum();
如果您这样做只是为了学习,那么正确的
减少将是(为了获得
总和
):


关键概念:标识、累加器和组合器

The method reduce(User, BinaryOperator<User>) in the type Stream<User> is not applicable for the arguments (int, (<no type> partialAgeResult, <no type> user) -> {})
Stream.reduce()操作:让我们将操作的参与者元素分解为单独的块。这样,我们就可以更容易地理解每个人所扮演的角色

  • 标识–一个元素,是还原操作的初始值,如果流为空,则为默认结果
  • 项累加器–一个采用两个参数的函数:还原操作的部分结果和流的下一个元素
  • 组合器–采用两个参数的函数:还原操作的部分结果和流的下一个元素 Combiner–一种函数,用于在还原被并行化时,或当累加器参数类型与累加器实现类型不匹配时,合并还原操作的部分结果
当一个流并行执行时,Java运行时将该流拆分为多个子流。在这种情况下,我们需要使用一个函数将子流的结果合并为一个结果。这是组合器的角色

案例1:组合器与
parallelStream
一起工作,如示例所示

案例2:具有不同类型参数的累加器示例

在本例中,我们有一个用户对象流,累加器参数的类型是Integer和User。但是,累加器实现是整数的和,因此编译器无法推断用户参数的类型

List<User> users = Arrays.asList(new User("John", 30), new User("Julie", 35));
int computedAges = users.stream().reduce(0, (partialAgeResult, user) -> partialAgeResult + user.getAge());

简单地说,如果我们使用顺序流和累加器参数的类型及其实现匹配的类型,我们不需要使用组合器。

有3种方法可以减少使用。简而言之,
Stream::reduce
从两个后续项(或一个标识值,其中一个包含第一个)开始,并使用它们执行一个操作,生成新的缩减值。对于下一个项目,同样的情况也会发生,并使用减少的值执行操作

假设您有一个
'a'
'b'
'c'
'd'
流。还原执行以下操作序列:

  • result=operationOn('a','b')
    -
    operationOn
    可以是任何东西(输入长度的总和..)
  • result=operation(结果'c')
  • result=operation(结果'd')
  • 返回结果
  • 这些方法是:

    • 对元素执行缩减。开始时,前两个项目产生一个缩减值,然后每个项目产生一个缩减值。由于不能保证输入流不为空,因此返回
      可选的

    • 执行与上述方法相同的操作,但标识值作为第一项给出。返回
      T
      ,因为由于
      T标识
      ,始终至少有一个项目得到保证

    • 执行与上述方法相同的操作,并添加一项功能组合。返回
      U
      ,因为由于
      U标识
      ,始终保证至少有一个项目


    组合器
    仅针对并行流调用。另外,请仔细阅读reduce的文档,您不能在
    累加器
    中进行求和,在
    组合器
    中进行乘法,并期望得到一些有意义的结果将标识值称为“默认值”是误导性的,甚至更糟。如果您想要一个默认值
    List<User> users = Arrays.asList(new User("John", 30), new User("Julie", 35));
    int computedAges = users.stream().reduce(0, (partialAgeResult, user) -> partialAgeResult + user.getAge());
    
    The method reduce(User, BinaryOperator<User>) in the type Stream<User> is not applicable for the arguments (int, (<no type> partialAgeResult, <no type> user) -> {})
    
    int computedAges = users.stream().reduce(0, (partialAgeResult, user) -> partialAgeResult + user.getAge(),Integer::sum);