在 Kotlin 的 `forEach` 中使用 `break` 和 `continue`

297

Kotlin拥有非常好的迭代函数,例如forEachrepeat,但我无法让breakcontinue操作符在它们上面工作(包括本地和非本地):

repeat(5) {
    break
}

(1..5).forEach {
    continue@forEach
}

目标是尽可能地使用函数式语法来模拟常规循环。在 Kotlin 的一些旧版本中肯定是可行的,但我很难复制这种语法。

问题可能是标签 (M12) 的一个 bug,但我认为第一个示例应该仍然可以工作。

我记得在某个地方读到过一个特殊的技巧/注释,但我找不到任何有关此主题的参考资料。可能看起来像以下内容:

public inline fun repeat(times: Int, @loop body: (Int) -> Unit) {
    for (index in 0..times - 1) {
        body(index)
    }
}

1
在当前的 Kotlin 中,您确实可以模仿这个功能(在等待 continue@labelbreak@label 功能时),请参见相关问题:https://dev59.com/sVsW5IYBdhLWcg3wuZRy - Jayson Minard
1
这个问题需要澄清一下,您是在询问函数式循环中是否存在breakcontinue,还是正在寻找完全相同的替代方案。前者似乎是正确的,因为您已经拒绝了后者。 - Jayson Minard
似乎他们在Kotlin 1.3中添加了这个。 - Tigran Babajanyan
@TigranBabajanyan 哇!你有链接吗? - voddan
@voddan,不,我刚试过了,它可以运行。 - Tigran Babajanyan
12个回答

313

这会打印出1到5。 return@forEach的作用类似于Java中的关键字continue,这意味着在这种情况下,它仍然执行每个循环,但如果值大于5,则跳到下一个迭代。

fun main(args: Array<String>) {
    val nums = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
    nums.forEach {
       if (it > 5) return@forEach
       println(it)
    }
}

这将打印出1到10,但跳过5。

fun main(args: Array<String>) {
    val nums = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
    nums.forEach {
       if (it == 5) return@forEach
       println(it)
    }
}

这将打印1到4,并在达到5时中断。

fun main(args: Array<String>) {
    val nums = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
    
    run breaking@ {
        nums.forEach {
           if (it == 5) return@breaking
           println(it)
        }
    }
}

链接到ashuges的代码片段


10
很好,但这仍然无法解决当满足某些条件时无法提前结束forEach的问题。它仍然会继续执行循环。 - The Fox
1
@TheFox 是的,它会执行每个循环,并且当条件满足时跳过 return 后面的任何内容。forEach 中的每个操作都是一个 lambda 函数,目前没有针对 forEach 操作的确切 break 操作。break 可用于 for 循环中,请参见:https://kotlinlang.org/docs/reference/returns.html - s-hunter
5
以下是带有 continuebreak 示例的可运行 Kotlin Playground 代码段:https://pl.kotl.in/_LAvET-wX - ashughes
他们重新定义了"goto" - gstackoverflow

220

编辑
根据 Kotlin 的文档,可以使用注解来模拟 continue

最初的回答: 根据 Kotlin 的文档,您可以使用注解来模拟 continue
fun foo() {
    listOf(1, 2, 3, 4, 5).forEach lit@ {
        if (it == 3) return@lit // local return to the caller of the lambda, i.e. the forEach loop
        print(it)
    }
    print(" done with explicit label")
}

如果你想模拟一个"break",只需添加一个"run"块。将Original Answer翻译成"最初的回答"。
fun foo() {
    run lit@ {
        listOf(1, 2, 3, 4, 5).forEach {
            if (it == 3) return@lit // local return to the caller of the lambda, i.e. the forEach loop
            print(it)
        }
        print(" done with explicit label")
    }
}

最初的回答:
由于您提供了一个 (Int) -> Unit,因此您无法从中断,因为编译器不知道它在循环中使用。

您有几个选项:

使用常规的 for 循环:

for (index in 0 until times) {
    // your code here
}

如果循环是方法中的最后一段代码,您可以使用return来退出该方法(如果不是unit方法,则使用return value)。
使用方法: 创建一个自定义的重复方法,该方法返回Boolean以继续执行。
public inline fun repeatUntil(times: Int, body: (Int) -> Boolean) {
    for (index in 0 until times) {
        if (!body(index)) break
    }
}

其实,我的问题是关于让特定的语法能够运行,不是关于迭代的。难道你不记得在 Kotlin 的某个里程碑版本中是有可能实现的吗? - voddan
1
我不记得了。但可能是因为我不经常使用break和continue。请参见此问题,它说“估计 - 没有估计”。 - Yoav Sternberg
2
breakcontinue只在循环中起作用。forEachrepeat和所有其他方法只是方法而不是循环。Yoav提出了一些替代方案,但breakcontinue并不适用于方法。 - Kirill Rakhman
@YoavSternberg 太棒了!这份旧文档正是我在寻找的!所以这个功能还没有实现,留待未来版本。如果您愿意创建一个单独的答案,我会标记它。 - voddan
在当前的 Kotlin 中,您确实可以模仿这个功能(在等待 continue@labelbreak@label 功能时),请参见相关问题:https://dev59.com/sVsW5IYBdhLWcg3wuZRy - Jayson Minard
显示剩余4条评论

82

使用以下方法可以进行休息:

//Will produce "12 done with nested loop"
//Using "run" and a tag will prevent the loop from running again.
//Using return@forEach if I>=3 may look simpler, but it will keep running the loop and checking if i>=3 for values >=3 which is a waste of time.
fun foo() {
    run loop@{
        listOf(1, 2, 3, 4, 5).forEach {
            if (it == 3) return@loop // non-local return from the lambda passed to run
            print(it)
        }
    }
    print(" done with nested loop")
}

可以通过以下方式实现代码的连续执行:

//Will produce: "1245 done with implicit label"
fun foo() {
    listOf(1, 2, 3, 4, 5).forEach {
        if (it == 3) return@forEach // local return to the caller of the lambda, i.e. the forEach loop
        print(it)
    }
    print(" done with implicit label")
}

作为这里的任何人所推荐的...阅读文档:P https://kotlinlang.org/docs/reference/returns.html#return-at-labels

编辑:虽然主要问题是关于forEach,但考虑使用老式的“for”很重要。使用Kotlin并不意味着我们必须始终使用forEach。使用老式的“for”是完全可以的,有时甚至比forEach更具表现力和简洁性:

fun foo() {
    for(x in listOf(1, 2, 3, 4, 5){
        if (x == 3) break //or continue
        print(x)
    }
    print("done with the good old for")
}

不错的解决方案。运行得非常好。尽管似乎不使用@loop也可以得到相同的期望结果。 - Paras Sidhu
实际上,您可以省略显式标签“@loop”,并使用隐式标签“@run”。这里的关键是将lambda的本地返回传递给调用者。请注意,您需要在某些范围内包装循环,以便稍后可以进行本地返回。 - Raymond Arteaga
这确实回答了问题,但我认为这可能不是函数式编程的正确路径。我们需要的是来自 Lodash 的 transform,其中您可以在某些条件下中断,例如 reduceReturnIf(acc, value, returnIf: func) - windmaomao

42

正如Kotlin文档所说,使用return是正确的方式。 Kotlin的好处在于,如果您有嵌套函数,可以使用标签来明确指定return从哪里返回:

函数作用域返回
fun foo() {
    listOf(1, 2, 3, 4, 5).forEach {
        /** Non-local return directly to the caller of foo(). */
        if (it == 3) return
        print(it)
    }

    println("this point is unreachable")
}
局部返回

它不会停止通过forEach循环(就像在for循环中的continue一样)。

fun foo() {
    listOf(1, 2, 3, 4, 5).forEach lit@{
        /** Local return to the caller of the lambda, i.e. the forEach loop. */
        if (it == 3) return@lit
        print(it)
    }

    print(" done with explicit label")
}

看一下文档,真的很好 :)


4
警告:return@lit 不会停止 forEach - Jemshit Iskenderov
这是正确的。这是预期的。第一种解决方案可以实现,但如果你在循环中有指令,你可以选择要返回/跳转到哪里。在第二种情况下,如果我们只使用return,它会停止 ;-) - cesards
3
调用 Return@lit 就像调用 Continue 一样。 - pqtuan86

31

19

forEach 中的 continue 类型行为

list.forEach { item -> // here forEach give you data item and you can use it 
    if () {
        // your code
        return@forEach // Same as continue
    }

    // your code
}

要实现类似于break的行为,您需要根据列表是Nullable还是Non-Nullable,使用for in untilfor in

  • 对于 Nullable 列表:

  • for (index in 0 until list.size) {
        val item = list[index] // you can use data item now
        if () {
            // your code
            break
        }
    
        // your code
    }
    
  • 对于非空列表:

    for (item in list) { // data item will available right away
        if () {
            // your code
            break
        }
    
        // your code
    }
    

  • 8
    我可以为此提供完美的解决方案 (:
    list.apply{ forEach{ item ->
        if (willContinue(item)) return@forEach
        if (willBreak(item)) return@apply
    }}
    

    2

    嵌套循环forEach()的中断语句:

    listOf("a", "b", "c").forEach find@{ i ->
        listOf("b", "d").forEach { j ->
            if (i == j) return@find
            println("i = $i, j = $j")
        }
    }
    

    结果:

    i = a, j = b
    i = a, j = d
    i = c, j = b
    i = c, j = d
    

    使用匿名函数的continue语句:

    listOf(1, 2, 3, 4, 5).forEach(fun(value: Int) {
        if (value == 3) return
        print("$value ")
    })
    

    结果:

    1 2 4 5 
    

    2
    也许可以将forEach更改为以下内容:
    for (it in myList) {
        if (condition) {
            doSomething()
        } else {
            break // or continue
        }
    } 
    

    这适用于 HashMap

     for (it in myMap) {
         val k = it.key
         val v = it.value
    
         if (condition) {
             doSomething()
         } else {
             break // or continue
         }
     }
    

    1
    我认为这是使用break或continue的正确替代方案。 - pseudoankit
    1
    不幸的是,这是唯一真正通用的解决方案。其他答案只在某些情况下有效。 - SMBiggs

    1

    看起来最好像这样使用For循环:for (item in list){ break }


    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接