类:汇总操作

注意 :要更好地理解本节中的概念,请查看Lambda ExpressionsMethod References部分。

您使用什么收藏?您不只是将对象存储在集合中并留在其中。在大多数情况下,您可以使用集合来检索存储在其中的项目。

再次考虑Lambda Expressions部分中描述的方案。假设您正在创建一个社交网络应用程序。您想创建一个功能,使 管理 员可以对满足特定条件的社交网络应用程序成员执行任何类型的操作,例如发送消息。

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

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 循环的版本 Long,但是您将看到使用批量数据操作的版本对于更复杂的任务将更加简洁。

涵盖以下主题:

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

管道和流

管道是一系列聚合操作。以下示例使用包含聚合操作filterforEach的管道来打印集合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 *是一系列元素。与集合不同,它不是存储元素的数据结构。相反,流通过管道从源携带值。本示例通过调用方法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.)

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

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

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

average操作计算类型为IntStream的流中包含的元素的平均值。它返回类型为OptionalDouble的对象。如果流不包含任何元素,则average操作将返回OptionalDouble的空实例,并且调用方法getAsDouble会引发NoSuchElementException。 JDK 包含许多终端操作,例如average,它们通过组合流的内容来返回一个值。这些操作称为还原操作;有关更多信息,请参见Reduction部分。

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

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

  • 它们使用内部迭代 :聚合操作不包含next之类的方法来指示它们处理集合的下一个元素。使用内部委托,您的应用程序确定集合进行迭代,而 JDK 确定如何迭代该集合。通过外部迭代,您的应用程序既可以确定要迭代的集合,又可以确定其迭代方式。但是,外部迭代只能 Sequences 地迭代集合的元素。内部迭代没有此限制。它可以更轻松地利用并行计算的优势,这涉及将一个问题分解为多个子问题,同时解决这些问题,然后将这些解决方案的结果组合为子问题。有关更多信息,请参见Parallelism部分。

    • 它们从流中处理元素 :聚合操作从流中而不是直接从集合中处理元素。因此,它们也称为流操作

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