注意:为了更好地理解本节中的概念,请查阅 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示例中找到本节中描述的代码摘录。

管道和流

管道(pipeline)是总的操作顺序。下面的例子打印roster集合中包含的男性成员,使用由聚合操作的filterforEach构成的管道:

  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的终端操作会生成非流结果,例如基本类型值(如double值),集合,或者在forEach情况下,根本没有值。在此示例中,forEach操作的参数是lambda表达式e -> System.out.println(e.getName()),该表达式调用对象e上的getName方法。(Java运行时和编译器推断对象e的类型为Person。)

下面的例子计算所有男性成员的平均年龄,roster集合中包含的一个管道,是由中间操作filtermapToInt以及average构成:

  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通过组合流的内容返回一个值。这些操作称为归约操作(reduction operations) ; 有关更多信息,请参见归约部分 。

聚合操作和迭代器之间的差异

聚合操作(如forEach)看起来像迭代器。但是,它们有几个基本差异:

  • 它们使用内部迭代:聚合操作不包含类似next的方法来指示它们处理集合的下一个元素。使用内部委派(internal delegation),您的应用程序决定它遍历什么集合,但JDK决定如何遍历集合。使用外部迭代,您的应用程序会确定要迭代哪个集合以及如何迭代它。但是,外部迭代只能顺序地迭代集合的元素。内部迭代没有此限制。它可以更轻松地利用并行计算的优势,这涉及将问题分为子问题,同时解决这些问题,然后将解决方案的结果组合到子问题中。有关更多信息,请参见并行一节 。
  • 它们处理流中的元素:聚合操作从流中而不是直接从集合中处理元素。因此,它们也称为流操作
  • 它们支持将行为作为参数:您可以将lambda表达式指定为,大多数聚合操作的参数。这使您可以自定义特定聚合操作的行为。