原文链接:https://docs.oracle.com/javase/tutorial/collections/streams/index.html
想想您是怎么用集合的?您不是简单地将对象存储在集合中并将它们留在集合中。多数情况下,您使用集合来检索存储在其中的项。
再次考虑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-每个循环的版本要长,但是您将看到使用大容量数据操作的版本对于更复杂的任务将更加简洁。
管道和流
管道是聚合操作的序列。下面的示例使用由聚合操作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,会产生新的流。
流是元素的序列。与集合不同,它不是存储元素的数据结构。相反,流通过管道从源传递值。此示例通过调用方法流从集合名册中创建流。filter操作返回包含匹配其谓词的元素的新流(该操作的参数)。在本例中,谓词是lambda表达式e->e.getGender() == Person.Sex.MALE。如果对象e的性别字段是Person.Sex.MALE,则返回布尔值true。因此,在此示例中的filter操作返回包含集合roster中所有男性成员的流。 - 终结操作
终结操作(如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类型的对象。如果流不包含任何元素,则平均操作返回OptionalDouble的一个空实例,调用方法getAsDouble将抛出NoSuchElementException。JDK包含许多终结操作,例如通过组合流的内容返回一个值的平均值。这些操作称为reduction操作。
聚合操作和迭代器的不同
像forEach这样的聚合操作看起来像迭代器。但是,它们有几个基本的差异:
- 它们使用内部迭代:聚合操作不包含像next这样的方法来指示它们处理集合的下一个元素。使用内部委托,应用程序确定它迭代的集合,但JDK决定如何迭代集合。使用外部迭代,您的应用程序将确定它迭代的集合以及它如何迭代它。但是,外部迭代只能对集合的元素进行顺序迭代。内部迭代没有这个限制。它可以更容易地利用并行计算的优点,将问题分解为子问题,同时解决这些问题,然后将子问题的解的结果结合起来。
- 它们处理来自流的元素:聚合操作处理流中的元素,而不是直接来自集合。因此,它们也被称为流操作。
- 它们支持行为作为参数。可以将lambda表达式指定为大多数聚合操作的参数。这使您能够自定义特定聚合操作的行为。