在 kotlin 的 forEach 如何实现 break 的效果?
官网文档的给出的代码是这样的
fun foo() {
run loop@{
listOf(1, 2, 3, 4, 5).forEach {
if (it == 3) return@loop // 从传入 run 的 lambda 表达式非局部返回
print(it)
}
}
print(" done with nested loop")
}
😲 这是什么鬼代码
先回顾一下break/continue
kotlin 的 break/continue 和 java 的作用是一样的
fun testBreak() {
for (i in 1..5) {
if (i > 3) break
println(i)
}
println("end")
}
------------输出---------------
1,2,3,end
------------------------------
fun testContinue() {
for (i in 1..5) {
if (i == 3) continue
println(i)
}
println("end")
}
------------输出---------------
1,2,4,5,end
------------------------------
指定标签
在 kotlin 中可以对表达式设置标签 以标签名加 @ 的形式,例如
loop@ for(i in 1..10){……}
loop 就是我们指定的标签名,后面紧跟一个 @ 符号
标签的作用,可以让我们返回或跳转到对应标签的位置,例如两层循环
fun testLabel() {
// ⬇️ 最外层循环设置一个loop1标签
loop1@ for (i in 1..5) {
for (j in 1..3) {
if (j>1) break@loop1 // 0️⃣
print("($i,$j),")
}
println()
print("i=$i,")
}
print("end")
}
------------输出---------------
(1,1),end
------------------------------
在代码 0️⃣ 处 break@loop1 那么就是终止最外层循环,如果 0️⃣ 处写的是 break 那么只会终止内部的循环,外部循环还会继续的。
forEach如何使用break/continue
在普通的 for 循环中利用 break/continue 可以很好的控制,那 forEach 该如何使用
一般使用 forEach 如下所示
fun testForEach(){
listOf(1,2,3,4,5).forEach {
print("$it,")
}
print("end")
}
------------输出---------------
1,2,3,4,5,end
------------------------------
你会发现在 forEach 里面 break/continue 都不能用,那怎么终止循环或跳过这次循环呢 ?
在 forEach 可以使用 return ,其效果如下
fun testForEach(){
listOf(1,2,3,4,5).forEach {
println("循环中……")
if (it==2) return
println("$it,")
}
print("end")
}
------------输出---------------
循环中……
1,
循环中……
------------------------------
⚠️ 看效果好像和 break 一样,循环终止了没有执行, 但仔细看会发现 ”end“ 没有输出,这是怎么回事?这里的 return 其实是函数的 return 函数如果 return 了,那么 return 下面的都不会执行,所以 ”end“ 没有打印。
我们修改一下代码再看一下
fun testForEach(){
listOf(1,2,3,4,5).forEach {
println("循环中……")
if (it==2) return@forEach
println("$it,")
}
print("end")
}
------------输出---------------
循环中……
1,
循环中……
循环中……
3,
循环中……
4,
循环中……
5,
end
------------------------------
这次写的是 return@forEach 表示是是否终止这次 lambda 的进行执行,for 循环还会继续,这种写法和 continue 的效果是一致的
👇 这是kotlin中的forEach源码
public inline fun <T> Iterable<T>.forEach(action: (T) -> Unit): Unit {
for (element in this) action(element)
}
forEach 源码很简单,就是循环执行 action 这个函数,这个 action 就是我们传入的 lambda,所有我们 return@forEach 只会影响一次,整体的 for 循环不会被终止的。
return@forEach 和 continue 效果一致,而 return 会让整个函数终止,那么要实现 break 该怎么办,官网文档说可以增加另一层嵌套 lambda 表达式并从其中非局部返回来模拟
fun testForEach(){
run {
listOf(1,2,3,4,5).forEach{
println("循环中……")
if (it==2) return@run
println("$it,")
}
}
print("end")
}
------------输出---------------
循环中……
1,
循环中……
end
------------------------------
这样看来和 break 效果一致,但感觉不够优雅呀。
思考 🤔
为何在 forEach 想使用 break/continue 就那么麻烦 ?
为何 kotlin 不在 forEach 里面支持 break/continue ?
我们对集合进行遍历,配合 break/continue 然后写一些逻辑 , 其目的一般都是操作集合而写的逻辑。
假设有这么一个问题:
给定一个集合如 [0,1,2,3,4,5] (集合中一定会有2这个元素)把元素为 2 之前的元素遍历出来
按照上面我们说的方式使用forEach实现如下
fun testForEach() {
run {
listOf(0,1, 2, 3, 4, 5).forEach{
if (it == 2) return@run
println(it)
}
}
……
}
------------输出---------------
0
1
------------------------------
我们用着 forEach 这种高阶函数,却按照以前的思考的方式去写代码,显然代码不够优雅。条条大路通罗马,既然 forEach 对 break/continue 那么不友好,我们能不能换种思路去看一下问题呢。问题给定一个集合输出集合中的一部分。这一输入输出的不经让我想起了函数式编程,毕竟 kotlin 也是支持函数式编程的语言,而且在集合框架中提供了需要操作的函数。

上图内容是 kotlin 中文网文档集合部分的目录,从目录可以看出,提供的函数式相当丰富的。
如此多的函数,难道没有合适的吗?当然有 。下面使用takeWhile
这个函数来实现上述的问题:
fun testForEach() {
listOf(0, 1, 2, 3, 4, 5).takeWhile { it != 2 }.forEach { println(it) }
……
}
------------输出---------------
0
1
------------------------------
takeWhile 表示从头开始取,一直取到不满足条件的,所以从头开始2之前的元素都被取出来了。使用集合框架api的就可以很轻松的搞定。
上面我说的这个例子可能有些牵强,但我觉得这个值得我们去思考,当我们去处理一个集合的时候,可以先想想集合框架提供的函数是否可以解决,以函数式编程的方式,而不是在 forEach 中找 break/continue 的替代方案,毕竟 kotlin 也是支持函数式编程的语言,我们可以用函数式编程风格操作集合代替以前的方案。