Jakob Jenkov 2018-12-02
Java Stream API 提供了一种更加函数式编程的方式来遍历和处理集合中的元素。该 API 自 Java 8 起被引入。
本教程仅旨在解释如何在 Java 集合(Collection)API 的上下文中使用 Java Stream API。如需更深入地了解 Java Stream API,请参阅我的 《Java Stream API 教程》。
Stream 被设计为与 Java Lambda 表达式 协同工作。本文中的许多示例都会使用 Lambda 表达式,因此如果你还不熟悉 Lambda 表达式,请先学习相关知识再继续阅读本文。
从集合获取 Stream
你可以通过调用集合的 stream() 方法来获取一个 Stream。以下是从集合获取 Stream 的示例:
List<String> items = new ArrayList<String>();
items.add("one");
items.add("two");
items.add("three");
Stream<String> stream = items.stream();
首先创建了一个字符串 List 并添加了三个字符串。然后通过调用 items.stream() 方法获取了一个字符串 Stream。这类似于通过调用 items.iterator() 获取 Iterator,但 Stream 与 Iterator 是完全不同的概念。
Stream 处理阶段
一旦从 Collection 实例中获取了 Stream 实例,就可以使用该 Stream 来处理集合中的元素。
Stream 的处理分为两个步骤/阶段:
- 配置(Configuration)
- 处理(Processing)
配置阶段
在配置阶段,你可以对 Stream 进行过滤(filter)和映射(map)等操作。这些操作也被称为 非终止操作(non-terminal operations)。
处理阶段
在处理阶段,会对经过过滤和映射后的对象执行具体操作。在调用处理方法之前,配置阶段的操作不会真正执行。Stream 的处理方法也称为 终止操作(terminal operations)。
Stream.filter()
你可以使用 filter() 方法对 Stream 进行过滤。例如:
stream.filter(item -> item.startsWith("o"));
filter() 方法接收一个 Predicate 作为参数。Predicate 接口包含一个名为 test() 的方法,上面传入的 Lambda 表达式就实现了这个方法。
test() 方法定义如下:
boolean test(T t)
它接收一个参数并返回一个布尔值。观察上面的 Lambda 表达式,它接收一个 item 参数,并返回 item.startsWith("o") 的结果。
调用 filter() 时,传入的过滤条件会被内部保存,此时并不会立即执行过滤。
传给 filter() 的参数决定了 Stream 中哪些元素应被处理、哪些应被排除。如果 Predicate.test() 对某个元素返回 true,则该元素会被保留;否则将被丢弃。
Stream.map()
你可以将集合中的元素映射为其他对象。换句话说,你可以基于每个元素创建一个新对象。具体的映射方式由你决定。下面是一个简单的 Java Stream 映射示例:
items.stream()
.map(item -> item.toUpperCase())
此示例将 items 集合中的所有字符串转换为其大写形式。
同样,这个示例 并未立即执行映射,只是配置了 Stream 的映射行为。只有在调用某个 Stream 处理方法后,映射(以及过滤)才会真正执行。
Stream.collect()
collect() 是 Stream 接口中的一个处理方法(终止操作)。当调用该方法时,会执行前面配置的过滤和映射操作,并将结果收集起来。
以下是一个 stream.collect() 的示例:
List<String> filtered = items.stream()
.filter(item -> item.startsWith("o"))
.collect(Collectors.toList());
该示例创建了一个 Stream,添加了一个过滤器,并将所有通过过滤器的元素收集到一个 List 中。过滤器只保留以字符 "o" 开头的字符串。因此,最终的 List 包含 items 集合中所有以 "o" 开头的字符串。
Stream.min() 和 Stream.max()
min() 和 max() 也是 Stream 的处理方法。一旦调用它们,Stream 就会被遍历,应用过滤和映射操作,并返回 Stream 中的最小值或最大值。
以下是 Stream.min() 的示例:
String shortest = items.stream()
.min(Comparator.comparing(item -> item.length()))
.get();
min() 和 max() 方法返回一个 Optional 实例,你可以通过其 get() 方法获取实际值。如果 Stream 中没有元素,get() 将返回 null。
这两个方法接收一个 Comparator 作为参数。Comparator.comparing() 方法根据传入的 Lambda 表达式创建一个 Comparator。实际上,comparing() 接收的是一个 Function 函数式接口,非常适合用 Lambda 表达式实现:它接收一个参数并返回一个值。
Stream.count()
count() 方法简单地返回 Stream 中经过过滤后的元素数量。示例如下:
long count = items.stream()
.filter(item -> item.startsWith("t"))
.count();
该示例遍历 Stream,保留所有以字符 "t" 开头的元素,然后统计这些元素的数量。
count() 方法返回一个 long 类型的值,表示过滤等操作后 Stream 中的元素总数。
Stream.reduce()
reduce() 方法可以将 Stream 中的所有元素归约为单个值。示例如下:
String reduced2 = items.stream()
.reduce((acc, item) -> acc + " " + item)
.get();
reduce() 方法接收一个 BinaryOperator 作为参数,可以用 Lambda 表达式轻松实现。BinaryOperator.apply() 方法正是上述 Lambda 表达式所实现的方法。它接收两个参数:acc(累积值)和 item(Stream 中的当前元素)。因此,reduce() 返回的是处理完最后一个元素后的累积结果。在上面的例子中,每个元素都被拼接到累积值后面。
这个版本的 reduce() 返回一个 Optional。如果 Stream 为空,则 Optional.get() 返回 null;否则返回归约后的值。
还有另一个 reduce() 方法接收两个参数:初始累积值和 BinaryOperator。示例如下:
String reduced = items.stream()
.reduce("", (acc, item) -> acc + " " + item);
此示例使用空字符串作为初始值,并使用与前例相同的 Lambda 表达式。这个版本的 reduce() 直接返回累积值,而不是 Optional。如果 Stream 为空,则返回初始值。
reduce() 也可以与 filter() 结合使用:
String reduced = items.stream()
.filter(item -> item.startsWith("o"))
.reduce("", (acc, item) -> acc + " " + item);
此示例保留所有以 "o" 开头的元素,并将它们归约为一个字符串。