# 课程：聚合操作

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

**注意**：为了更好地理解本节中的概念，请查看 [Lambda 表达式](../../java/javaOO/lambdaexpressions.html)和[方法参考](../../java/javaOO/methodreferences.html)部分。

你用什么收藏品？您不是简单地将对象存储在集合中并将其保留在那里。在大多数情况下，您使用集合来检索存储在其中的项目。

再次考虑 [Lambda 表达式](../../java/javaOO/lambdaexpressions.html)部分中描述的场景。假设您正在创建社交网络应用程序。您希望创建一项功能，使管理员能够对满足特定条件的社交网络应用程序成员执行任何类型的操作，例如发送消息。

和以前一样，假设此社交网络应用程序的成员由以下 [`Person`](examples/Person.java) 类表示：

```
public class Person {

    public enum Sex {
        MALE, FEMALE
    }

    String name;
    LocalDate birthday;
    Sex gender;
    String emailAddress;

    // ...

    public int getAge() {
        // ...
    }

    public String getName() {
        // ...
    }
}
```

以下示例使用 for-each 循环打印集合`roster`中包含的所有成员的名称：

```
for (Person p : roster) {
    System.out.println(p.getName());
}
```

以下示例打印集合`roster`中包含但包含聚合操作`forEach`的所有成员：

```
roster
    .stream()
    .forEach(e -> System.out.println(e.getName());
```

虽然在此示例中，使用聚合操作的版本比使用 for-each 循环的版本长，但您会看到使用批量数据操作的版本对于更复杂的任务将更简洁。

涵盖以下主题：

*   [管道和流](#pipelines)
*   [聚合操作和迭代器之间的差异](#differences)

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

_ 管道 _ 是一系列聚合操作。以下示例使用由聚合操作`filter`和`forEach`组成的管道打印集合`roster`中包含的男性成员：

```
roster
    .stream()
    .filter(e -> e.getGender() == Person.Sex.MALE)
    .forEach(e -> System.out.println(e.getName()));
```

将此示例与以下内容进行比较，以使用 for-each 循环打印集合`roster`中包含的男性成员：

```
for (Person p : roster) {
    if (p.getGender() == Person.Sex.MALE) {
        System.out.println(p.getName());
    }
}
```

管道包含以下组件：

*   源：这可以是集合，数组，生成器函数或 I / O 通道。在此示例中，源是集合`roster`。

*   零个或多个 _ 中间操作 _。中间操作（例如`filter`）会生成新流。

    _ 流 _ 是元素序列。与集合不同，它不是存储元素的数据结构。相反，流通过管道从源传输值。此示例通过调用方法`stream`从集合`roster`创建流。

    `filter`操作返回一个新流，其中包含与其谓词匹配的元素（此操作的参数）。在此示例中，谓词是 lambda 表达式`e -&gt; e.getGender() == Person.Sex.MALE`。如果对象`e`的`gender`字段具有值`Person.Sex.MALE`，则返回布尔值`true`。因此，此示例中的`filter`操作返回包含集合`roster`中所有男性成员的流。

*   _ 端子操作 _。终端操作（例如`forEach`）产生非流结果，例如原始值（如双值），集合，或者在`forEach`的情况下，根本没有值。在此示例中，`forEach`操作的参数是 lambda 表达式`e -&gt; System.out.println(e.getName())`，它调用对象`e`上的方法`getName`。 （Java 运行时和编译器推断对象的类型`e`是`Person`。）

以下示例使用由聚合操作`filter`，`mapToInt`和`average`组成的管道计算集合`roster`中包含的所有男性成员的平均年龄：

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

`mapToInt`操作返回`IntStream`类型的新流（它是仅包含整数值的流）。该操作将其参数中指定的函数应用于特定流中的每个元素。在此示例中，函数是`Person::getAge`，它是返回成员年龄的方法引用。 （或者，您可以使用 lambda 表达式`e -&gt; e.getAge()`。）因此，此示例中的`mapToInt`操作返回包含集合`roster`中所有男性成员的年龄的流。

`average`操作计算`IntStream`类型的流中包含的元素的平均值。它返回`OptionalDouble`类型的对象。如果流不包含元素，则`average`操作返回`OptionalDouble`的空实例，并且调用方法`getAsDouble`会抛出`NoSuchElementException`。 JDK 包含许多终端操作，例如`average`，它通过组合流的内容返回一个值。这些操作称为 _ 减少操作 _;有关详细信息，请参阅[减少](../../collections/streams/reduction.html)部分。

聚合操作，如`forEach`，看起来像迭代器。但是，它们有几个根本区别：

*   **他们使用内部迭代**：聚合操作不包含像`next`这样的方法来指示他们处理集合的下一个元素。使用 _ 内部委托 _，您的应用程序确定 _ 它迭代的 _ 集合，但 JDK 确定 _ 如何 _ 迭代集合。使用 _ 外部迭代 _，您的应用程序将确定它迭代的集合以及迭代它的方式。但是，外部迭代只能按顺序迭代集合的元素。内部迭代没有此限制。它可以更容易地利用并行计算，这涉及将问题分解为子问题，同时解决这些问题，然后将解决方案的结果与子问题相结合。有关更多信息，请参见 [Parallelism](../../collections/streams/parallelism.html) 部分。

*   **他们处理来自流**的元素：聚合操作处理来自流的元素，而不是直接来自集合。因此，它们也被称为 _ 流操作 _。

*   **它们支持行为作为参数**：您可以将 [lambda 表达式](../../java/javaOO/lambdaexpressions.html)指定为大多数聚合操作的参数。这使您可以自定义特定聚合操作的行为。