写点什么

Java- 技术专题 -Stream.foreach 和 foreach

发布于: 2021 年 04 月 15 日
Java-技术专题-Stream.foreach和foreach

1.简介

java 中有多种方式对集合进行遍历。本教程中将看两个类似的方法


Collection.stream().forEach()和Collection.forEach()
复制代码

在大多数情况下,两者都会产生相同的结果,但是,我们会看到一些微妙的差异。

2.概述

首先,创建一个迭代列表:

List<String> list = Arrays.asList("A","B","C","D");
复制代码

最直接的方法是使用增强的 for 循环:


for(String s: list){
//do something with s
}
复制代码

如果我们想使用函数式 Java,我们也可以使用 forEach()。我们可以直接在集合上这样做:

Consumer<String> consumer = s->{System.out::println};list.forEach(consumer);
复制代码

或者,我们可以在集合的流上调用 forEach():

list.stream().forEach(consumer);
复制代码

两个版本都将迭代列表并打印所有元素:

ABCDABCD
复制代码

在这个简单的例子中,我们使用的 forEach()没有区别。

3.执行顺序

Collection.forEach()使用集合的迭代器(如果指定了一个),集合里元素的处理顺序是明确的。相反,Collection.stream().forEach()的处理顺序是不明确的

在大多数情况下,我们选择上述两种方式的哪一种是没有区别的。但是有时候有。

3.1 Parallel Stream

并发流允许我们在多个线程中执行 stream,在这种情况下,执行顺序也不明确的。Java 只需要在调用任何最终操作(例如 Collectors.toList())之前完成所有线程。

看一个例子,首先直接在集合上调用 forEach(),然后在并发流上调用:

list.forEach(System.out::print);list.parallalStream().forEach(System.out::print);
复制代码

我们会看到 list.forEach()以插入顺序处理元素,而 list.parallelStream().forEach()在每次运行会产生不同的结果。一个可能的输出是:

ABCD CDBAABCD DBCA
复制代码

3.2 自定义迭代器

让我们使用自定义迭代器定义一个列表,以反向顺序迭代集合:

当我们遍历列表时,再次使用 forEach()直接在集合上,然后在流上:

List<String> myList = new ReverseList();myList.addAll(list);myList.forEach(System.out::print);myList.stream().forEach(System.out::print);
复制代码

我们得到不同的结果:

DBCA ABCD
复制代码

结果不同的原因是在列表中使用的 forEach()会使用自定义迭代器,而 stream().forEach()只是从列表中逐个获取元素,会忽略迭代器。

4.修改集合

很多集合在遍历的时候,不应该在结构上被修改(比如 ArrayList 或 HashSet)。如果在迭代期间删除或添加元素,会抛出 ConcurrentModification 异常。

此外,集合设计为快速失败(fail-fast),这意味着一旦修改就抛出异常。

类似地,当我们在 stream 的执行期间添加或删除元素时,我们将得到 ConcurrentModification 异常。但是,异常将在稍后抛出。

两个 forEach()方法之间的另一个细微差别是 Java 明确允许使用迭代器修改元素。相反,stream 不能。来看一下更详细的例子。

4.1 删除元素

定义一个列表,删除最后一个元素(“D”):

遍历列表时,在打印第一个元素(“A”)后删除最后一个元素:

list.forEach(removeElement);
复制代码

因为 forEach()是快速失败的,所以我们停止迭代并在处理下一个元素之前看到异常:

让我们看看如果我们使用 stream().forEach()会发生什么:

list.stream().forEach(removeElement);
复制代码

在这里,我们继续迭代整个列表,然后才看到异常:

但是,Java 并不保证会抛出 ConcurrentModificationException。这意味着我们永远不应该编写依赖于此异常的程序。

4.2 改变元素

我们可以在迭代列表时更改元素:

list.forEach(e->{
list.set(3,"E");
})
复制代码

但是,虽然使用 Collection.forEach()或 stream().forEach()执行此操作没有问题,但 Java 要求对流的操作是无干扰的。这意味着在执行流管道期间不应修改元素。这背后的原因是流应该促进并行执行。在这里,修改流的元素可能会导致意外行为。

5.结论

在本文中,我们看到了一些示例,它们显示了 Collection.forEach()和 Collection.stream().forEach()之间的细微差别。但是,重要的是要注意上面显示的所有示例仅仅是为了比较迭代集合的两种方式。

如果我们不需要流但只想迭代集合,则第一个选择应该直接在集合上使用 forEach()。


用户头像

我们始于迷惘,终于更高水平的迷惘。 2020.03.25 加入

🏆 【酷爱计算机技术、醉心开发编程、喜爱健身运动、热衷悬疑推理的”极客狂人“】 🏅 【Java技术领域,MySQL技术领域,APM全链路追踪技术及微服务、分布式方向的技术体系等】 🤝未来我们希望可以共同进步🤝

评论

发布
暂无评论
Java-技术专题-Stream.foreach和foreach