# 减少

> 原文： [https://docs.oracle.com/javase/tutorial/collections/streams/reduction.html](https://docs.oracle.com/javase/tutorial/collections/streams/reduction.html)

[聚合操作](../../collections/streams/index.html)部分描述了以下操作流程，它计算集合`roster`中所有男性成员的平均年龄：

```
double average = roster
    .stream()
    .filter(p -> p.getGender() == Person.Sex.MALE)
    .mapToInt(Person::getAge)
    .average()
    .getAsDouble();
```

JDK 包含许多终端操作（例如 [`average`](https://docs.oracle.com/javase/8/docs/api/java/util/stream/IntStream.html#average--java/lang/reflect/Executable.html) ， [`sum`](https://docs.oracle.com/javase/8/docs/api/java/util/stream/IntStream.html#sum--) ， [`min`](https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html#min-java.util.Comparator-) ， [`max`](https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html#max-java.util.Comparator-) ，以及 [`count`](https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html#count--) ）通过组合流的内容返回一个值。这些操作称为 _ 还原操作 _。 JDK 还包含返回集合而不是单个值的缩减操作。许多还原操作执行特定任务，例如查找值的平均值或将元素分组到类别中。但是，JDK 为您提供了通用的还原操作 [`reduce`](https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html#reduce-T-java.util.function.BinaryOperator-) 和 [`collect`](https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html#collect-java.util.function.Supplier-java.util.function.BiConsumer-java.util.function.BiConsumer-) ，本节将详细介绍。

本节包括以下主题：

*   [Stream.reduce 方法](#reduce)
*   [Stream.collect 方法](#collect)

您可以在示例 [`ReductionExamples`](examples/ReductionExamples.java) 中找到本节中描述的代码摘录。

[`Stream.reduce`](https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html#reduce-T-java.util.function.BinaryOperator-) 方法是通用的还原操作。考虑以下管道，该管道计算集合`roster`中男性成员年龄的总和。它使用 [`Stream.sum`](https://docs.oracle.com/javase/8/docs/api/java/util/stream/IntStream.html#sum--) 减少操作：

```
Integer totalAge = roster
    .stream()
    .mapToInt(Person::getAge)
    .sum();
```

将此与以下管道进行比较，该管道使用`Stream.reduce`操作来计算相同的值：

```
Integer totalAgeReduce = roster
   .stream()
   .map(Person::getAge)
   .reduce(
       0,
       (a, b) -> a + b);
```

此示例中的`reduce`操作有两个参数：

*   `identity`：如果流中没有元素，则 identity 元素既是缩小的初始值又是默认结果。在这个例子中，标识元素是`0`;如果集合`roster`中不存在成员，则这是年龄总和的初始值和默认值。

*   `accumulator`：累加器函数有两个参数：减少的部分结果（在本例中，到目前为止所有处理过的整数的总和）和流的下一个元素（在本例中为整数）。它返回一个新的部分结果。在此示例中，累加器函数是一个 lambda 表达式，它添加两个`Integer`值并返回`Integer`值：

    ```
    (a, b) -&gt; a + b
    ```

`reduce`操作始终返回新值。但是，累加器函数每次处理流的元素时也会返回一个新值。假设您要将流的元素减少为更复杂的对象，例如集合。这可能会妨碍您的应用程序的性能。如果您的`reduce`操作涉及向集合添加元素，那么每次您的累加器函数处理元素时，它都会创建一个包含该元素的新集合，这是低效的。相反，更新现有集合会更有效。您可以使用 [`Stream.collect`](https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html#collect-java.util.function.Supplier-java.util.function.BiConsumer-java.util.function.BiConsumer-) 方法执行此操作，下一节将介绍该方法。

与`reduce`方法不同，`reduce`方法在处理元素时始终创建新值， [`collect`](https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html#collect-java.util.function.Supplier-java.util.function.BiConsumer-java.util.function.BiConsumer-) 方法修改或改变现有值。

考虑如何在流中查找值的平均值。您需要两个数据：值的总数和这些值的总和。但是，与`reduce`方法和所有其他还原方法一样，`collect`方法只返回一个值。您可以创建一个包含成员变量的新数据类型，这些成员变量可跟踪值的总数以及这些值的总和，例如以下类 [`Averager`](examples/Averager.java) ：

```
class Averager implements IntConsumer
{
    private int total = 0;
    private int count = 0;

    public double average() {
        return count > 0 ? ((double) total)/count : 0;
    }

    public void accept(int i) { total += i; count++; }
    public void combine(Averager other) {
        total += other.total;
        count += other.count;
    }
}
```

以下管道使用`Averager`类和`collect`方法计算所有男性成员的平均年龄：

```
Averager averageCollect = roster.stream()
    .filter(p -> p.getGender() == Person.Sex.MALE)
    .map(Person::getAge)
    .collect(Averager::new, Averager::accept, Averager::combine);

System.out.println("Average age of male members: " +
    averageCollect.average());
```

此示例中的`collect`操作有三个参数：

*   `supplier`：供应商是工厂职能;它构造了新的实例。对于`collect`操作，它创建结果容器的实例。在此示例中，它是`Averager`类的新实例。
*   `accumulator`：累加器功能将流元素合并到结果容器中。在此示例中，它通过将`count`变量递增 1 并将`total`成员变量添加到 stream 元素的值来修改`Averager`结果容器，该元素是表示男性成员年龄的整数。
*   `combiner`：组合器函数接受两个结果容器并合并其内容。在此示例中，它通过将另一个`Averager`实例的`count`成员变量递增`count`变量并将另一个`Averager`的值添加到`total`成员变量来修改`Averager`结果容器。实例的`total`成员变量。

请注意以下事项：

*   供应商是 lambda 表达式（或方法引用），而不是像`reduce`操作中的 identity 元素那样的值。
*   累加器和组合器函数不返回值。
*   您可以对并行流使用`collect`操作;有关更多信息，请参见 [Parallelism](../../collections/streams/parallelism.html) 部分。 （如果使用并行流运行`collect`方法，那么每当组合器函数创建一个新对象时，JDK 就会创建一个新线程，例如本例中的`Averager`对象。因此，您不必担心同步。）

尽管 JDK 为您提供了`average`操作来计算流中元素的平均值，但如果需要从流的元素计算多个值，则可以使用`collect`操作和自定义类。

`collect`操作最适合收藏。以下示例使用`collect`操作将男性成员的名称放在集合中：

```
List<String> namesOfMaleMembersCollect = roster
    .stream()
    .filter(p -> p.getGender() == Person.Sex.MALE)
    .map(p -> p.getName())
    .collect(Collectors.toList());
```

此版本的`collect`操作采用 [`Collector`](https://docs.oracle.com/javase/8/docs/api/java/util/stream/Collector.html) 类型的一个参数。该类封装了在`collect`操作中用作参数的函数，该函数需要三个参数（供应商，累加器和组合器函数）。

[`Collectors`](https://docs.oracle.com/javase/8/docs/api/java/util/stream/Collectors.html) 类包含许多有用的缩减操作，例如将元素累积到集合中并根据各种标准汇总元素。这些缩减操作返回类`Collector`的实例，因此您可以将它们用作`collect`操作的参数。

此示例使用 [`Collectors.toList`](https://docs.oracle.com/javase/8/docs/api/java/util/stream/Collectors.html#toList--) 操作，该操作将流元素累积到`List`的新实例中。与`Collectors`类中的大多数操作一样，`toList`运算符返回`Collector`的实例，而不是集合。

以下示例按性别对集合`roster`的成员进行分组：

```
Map<Person.Sex, List<Person>> byGender =
    roster
        .stream()
        .collect(
            Collectors.groupingBy(Person::getGender));
```

[`groupingBy`](https://docs.oracle.com/javase/8/docs/api/java/util/stream/Collectors.html#groupingBy-java.util.function.Function-) 操作返回一个映射，其键是应用指定为其参数的 lambda 表达式（称为 _ 分类函数 _）得到的值。在此示例中，返回的映射包含两个键，`Person.Sex.MALE`和`Person.Sex.FEMALE`。密钥的对应值是包含流元素的`List`的实例，当由分类函数处理时，流元素对应于密钥值。例如，对应于键`Person.Sex.MALE`的值是包含所有男性成员的`List`的实例。

以下示例检索集合`roster`中每个成员的名称，并按性别对其进行分组：

```
Map<Person.Sex, List<String>> namesByGender =
    roster
        .stream()
        .collect(
            Collectors.groupingBy(
                Person::getGender,                      
                Collectors.mapping(
                    Person::getName,
                    Collectors.toList())));
```

本例中的 [`groupingBy`](https://docs.oracle.com/javase/8/docs/api/java/util/stream/Collectors.html#groupingBy-java.util.function.Function-java.util.stream.Collector-) 操作采用两个参数，即分类函数和`Collector`的实例。 `Collector`参数称为 _ 下游收集器 _。这是 Java 运行时应用于另一个收集器的结果的收集器。因此，此`groupingBy`操作使您可以将`collect`方法应用于`groupingBy`运算符创建的`List`值。此示例应用收集器 [`mapping`](https://docs.oracle.com/javase/8/docs/api/java/util/stream/Collectors.html#mapping-java.util.function.Function-java.util.stream.Collector-java.util.stream.Collector-) ，它将映射函数`Person::getName`应用于流的每个元素。因此，生成的流只包含成员的名称。包含一个或多个下游收集器的管道（如此示例）称为 _ 多级缩减 _。

以下示例检索每个性别的成员的总年龄：

```
Map<Person.Sex, Integer> totalAgeByGender =
    roster
        .stream()
        .collect(
            Collectors.groupingBy(
                Person::getGender,                      
                Collectors.reducing(
                    0,
                    Person::getAge,
                    Integer::sum)));
```

[`reducing`](https://docs.oracle.com/javase/8/docs/api/java/util/stream/Collectors.html#reducing-U-java.util.function.Function-java.util.function.BinaryOperator-) 操作有三个参数：

*   `identity`：与`Stream.reduce`操作类似，如果流中没有元素，则 identity 元素既是缩小的初始值又是默认结果。在这个例子中，标识元素是`0`;如果不存在成员，则这是年龄总和的初始值和默认值。
*   `mapper`：`reducing`操作将此映射器函数应用于所有流元素。在此示例中，映射器检索每个成员的年龄。
*   `operation`：操作功能用于减少映射值。在此示例中，操作函数添加`Integer`值。

以下示例检索每个性别的成员的平均年龄：

```
Map<Person.Sex, Double> averageAgeByGender = roster
    .stream()
    .collect(
        Collectors.groupingBy(
            Person::getGender,                      
            Collectors.averagingInt(Person::getAge)));
```