Java教程是为JDK 8编写的。本页面中描述的示例和实践不利用后续版本引入的改进,并可能使用不再可用的技术。
请参阅Java语言更改以获取Java SE 9和后续版本中更新的语言特性的摘要。
请参阅JDK发布说明以获取有关所有JDK版本的新功能、增强功能和已删除或弃用选项的信息。
注意:为了更好地理解本节中的概念,请查阅Lambda表达式和方法引用部分。
你用集合做什么?你不仅仅是把对象存储在集合中然后不管它们了。在大多数情况下,你使用集合来检索存储在其中的项。
再次考虑Lambda表达式部分中描述的情景。假设你正在创建一个社交网络应用程序。你想要创建一个功能,使管理员能够对满足特定条件的社交网络应用程序成员执行任何类型的操作,例如发送消息。
和之前一样,假设这个社交网络应用程序的成员由以下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());
}
以下示例使用聚合操作forEach打印出集合roster中的所有成员:
roster
.stream()
.forEach(e -> System.out.println(e.getName());
虽然在这个例子中,使用聚合操作的版本比使用for-each循环的版本更长,但你会发现,对于更复杂的任务,使用批量数据操作的版本会更简洁。
本节涵盖以下主题:
在示例BulkDataOperationsExamples中找到本节中描述的代码片段。
一个流水线是一系列的聚合操作。以下示例使用由聚合操作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 -> e.getGender() == Person.Sex.MALE。如果对象e的gender字段的值是Person.Sex.MALE,则返回布尔值true。因此,在这个示例中,filter操作返回一个包含集合roster中所有男性成员的流。
终止操作。终止操作,比如forEach,生成非流结果,比如原始值(比如双精度值)、集合,或者在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包含许多返回通过组合流的内容而得到一个值的终端操作,这些操作被称为规约操作;更多信息请参见规约部分。
聚合操作(如forEach)看起来像迭代器,但它们有几个根本的区别:
它们使用内部迭代:聚合操作不包含像next这样的方法来指示它们处理集合的下一个元素。通过内部委托,你的应用程序确定迭代的哪个集合,但JDK确定如何迭代集合。通过外部迭代,你的应用程序既确定迭代的集合,又确定如何迭代它。然而,外部迭代只能按顺序迭代集合的元素。内部迭代没有这个限制。它可以更容易地利用并行计算,即将一个问题分解为子问题,同时解决这些子问题,然后将子问题的解决结果合并起来。有关更多信息,请参见并行性部分。
它们从流中处理元素:聚合操作从流中处理元素,而不是直接从集合中处理。因此,它们也被称为流操作。
它们支持行为作为参数:你可以为大多数聚合操作指定lambda表达式作为参数。这使你能够自定义特定聚合操作的行为。