Java 教程是为 JDK 8 编写的。本页中描述的示例和实践未利用在后续版本中引入的改进。
注意:要更好地理解本节中的概念,请查看 Lambda Expressions 和 Method 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 循环的版本长,但你会看到使用批量数据操作的版本对于更复杂的任务将更简洁。
涵盖以下主题:
在示例 BulkDataOperationsExamples 中查找本节中描述的代码片段。
pipeline 是一系列聚合操作。以下示例使用由聚合操作 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 是一系列元素。与集合不同,它不是存储元素的数据结构。相反,流通过管道从源传输值。此示例通过调用方法 stream 从集合 roster 创建流。
filter 操作返回一个新流,其中包含与其谓词匹配的元素(此操作的参数)。在此示例中,谓词是 lambda 表达式 e -> e.getGender() == Person.Sex.MALE。如果对象 e 的 gender 字段的值为 Person.Sex.MALE,则返回布尔值 true。因此,此示例中的 filter 操作返回包含集合 roster 中所有男性成员的流。
terminal operation (终端操作)。终端操作(例如 forEach)会产生非流结果,例如基本类型值(如 double 值),集合,或者在 forEach 的情况下,没有任何值。在此示例中,forEach 操作的参数是 lambda 表达式 e -> 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 -> e.getAge()。)因此,此示例中的 mapToInt 操作返回一个流包含roster 集合中所有男性成员的年龄。
average 操作计算 IntStream 类型流中包含的元素的平均值。它返回一个 OptionalDouble 类型的对象。如果流不包含任何元素,则 average 操作返回 OptionalDouble 的空实例,并且调用方法 getAsDouble 会抛出 NoSuchElementException。JDK 包含许多终端操作,例如 average,它通过组合流的内容返回一个值。这些操作称为 reduction operations (还原操作);有关详细信息,请参阅 Reduction 部分。
聚合操作,如 forEach,看起来像迭代器。但是,它们有几个根本区别:
它们使用内部迭代:聚合操作不包含 next 之类的方法来指示他们处理集合的下一个元素。使用 内部委托,你的应用程序确定是 什么 集合,但 JDK 确定 如何 迭代集合。使用 外部迭代,你的应用程序将确定它迭代的集合以及迭代它的方式。但是,外部迭代只能按顺序迭代集合的元素。内部迭代没有此限制。它可以更容易地利用并行计算,这涉及将问题分解为子问题,同时解决这些问题,然后组合子问题解决方案的结果。有关更多信息,请参阅 Parallelism 部分。
它们处理流中的元素:聚合操作处理来自流的元素,而不是直接来自集合。因此,它们也称为 stream operations (流操作)。
它们支持行为作为参数:你可以将 lambda expressions 指定为大多数聚合操作的参数。这使你可以自定义特定聚合操作的行为。