注意:为了更好地理解本节中的概念,请查阅 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());
}
以下示例显示roster
集合中包含的所有成员,但使用有聚合操作的forEach
:
roster
.stream()
.forEach(e -> System.out.println(e.getName());
尽管在此示例中,使用聚合操作的版本比使用for-each循环的版本长,但是您将看到使用批量数据操作的版本对于更复杂的任务将更加简洁。
涵盖以下主题:
在BulkDataOperationsExamples
示例中找到本节中描述的代码摘录。
管道和流
管道(pipeline)是总的操作顺序。下面的例子打印roster
集合中包含的男性成员,使用由聚合操作的filter
和forEach
构成的管道:
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
的终端操作会生成非流结果,例如基本类型值(如double值),集合,或者在forEach
情况下,根本没有值。在此示例中,forEach
操作的参数是lambda表达式e -> System.out.println(e.getName())
,该表达式调用对象e
上的getName
方法。(Java运行时和编译器推断对象e
的类型为Person
。)
下面的例子计算所有男性成员的平均年龄,roster
集合中包含的一个管道,是由中间操作filter
,mapToInt
以及average
构成:
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) ; 有关更多信息,请参见归约部分 。
聚合操作和迭代器之间的差异
聚合操作(如forEach
)看起来像迭代器。但是,它们有几个基本差异:
- 它们使用内部迭代:聚合操作不包含类似
next
的方法来指示它们处理集合的下一个元素。使用内部委派(internal delegation),您的应用程序决定它遍历什么集合,但JDK决定如何遍历集合。使用外部迭代,您的应用程序会确定要迭代哪个集合以及如何迭代它。但是,外部迭代只能顺序地迭代集合的元素。内部迭代没有此限制。它可以更轻松地利用并行计算的优势,这涉及将问题分为子问题,同时解决这些问题,然后将解决方案的结果组合到子问题中。有关更多信息,请参见并行一节 。 - 它们处理流中的元素:聚合操作从流中而不是直接从集合中处理元素。因此,它们也称为流操作。
- 它们支持将行为作为参数:您可以将lambda表达式指定为,大多数聚合操作的参数。这使您可以自定义特定聚合操作的行为。