课程:聚合操作

原文: https://docs.oracle.com/javase/tutorial/collections/streams/index.html

注意:为了更好地理解本节中的概念,请查看 Lambda 表达式方法参考部分。

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

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

和以前一样,假设此社交网络应用程序的成员由以下 Person 类表示:

  1. public class Person {
  2. public enum Sex {
  3. MALE, FEMALE
  4. }
  5. String name;
  6. LocalDate birthday;
  7. Sex gender;
  8. String emailAddress;
  9. // ...
  10. public int getAge() {
  11. // ...
  12. }
  13. public String getName() {
  14. // ...
  15. }
  16. }

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

  1. for (Person p : roster) {
  2. System.out.println(p.getName());
  3. }

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

  1. roster
  2. .stream()
  3. .forEach(e -> System.out.println(e.getName());

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

涵盖以下主题:

在示例 BulkDataOperationsExamples 中找到本节中描述的代码摘录。

管道是一系列聚合操作。以下示例使用由聚合操作filterforEach组成的管道打印集合roster中包含的男性成员:

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

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

  1. for (Person p : roster) {
  2. if (p.getGender() == Person.Sex.MALE) {
  3. System.out.println(p.getName());
  4. }
  5. }

管道包含以下组件:

  • 源:这可以是集合,数组,生成器函数或 I / O 通道。在此示例中,源是集合roster

  • 零个或多个中间操作。中间操作(例如filter)会生成新流。

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

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

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

以下示例使用由聚合操作filtermapToIntaverage组成的管道计算集合roster中包含的所有男性成员的平均年龄:

  1. double average = roster
  2. .stream()
  3. .filter(p -> p.getGender() == Person.Sex.MALE)
  4. .mapToInt(Person::getAge)
  5. .average()
  6. .getAsDouble();

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

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

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

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

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

  • 它们支持行为作为参数:您可以将 lambda 表达式指定为大多数聚合操作的参数。这使您可以自定义特定聚合操作的行为。