在 Kotlin 中的功能循环中,如何“中断”或“继续”?

Posted

技术标签:

【中文标题】在 Kotlin 中的功能循环中,如何“中断”或“继续”?【英文标题】:How do I do a "break" or "continue" when in a functional loop within Kotlin? 【发布时间】:2016-04-11 02:50:35 【问题描述】:

在 Kotlin 中,我不能在函数循环和我的 lambda 中执行 breakcontinue —— 就像我可以在普通的 for 循环中一样。例如,这不起作用:

(1..5).forEach 
    continue@forEach  // not allowed, nor break@forEach

有old documentation 提到这是可用的,但它似乎从未实现过。当我想在 lambda 中 continuebreak 时,获得相同行为的最佳方法是什么?

注意: 此问题由作者 (Self-Answered Questions) 有意编写和回答,因此常见的 Kotlin 主题的惯用答案出现在 SO 中。还要澄清一些为 Kotlin alpha 编写的非常古老的答案,这些答案对于当今的 Kotlin 并不准确。

【问题讨论】:

注意:在 forEach 函数 lambda 的最后一行使用 return@forEach 时,最好提一下 IDE(android Studio 也可能是 Intellij)不显示警告。 【参考方案1】:

除了您所要求的以外,还有其他选项可以提供类似的功能。例如:

您可以避免使用filter 处理某些值:(就像continue

dataSet.filter  it % 2 == 0 .forEach 
    // do work on even numbers

您可以使用takeWhile 停止功能循环:(就像break

dataSet.takeWhile  it < 10 .forEach 
    // do work on numbers as long as they are < 10, otherwise stop

一个更复杂但荒谬的例子是:

dataSet.asSequence()
       .takeWhile  it >=  0     // a -1 signals end of the dataset (break)
       .map  it + 1             // increment each number
       .filterNot  it % 5 == 0  // skip (continue) numbers divisible by 5
       .map  it - 1             // decrement each number by 1
       .filter  it < 100        // skip (continue) if number is >= 100
       .drop(5)                   // ignore the first 5 numbers
       .take(10)                  // use the next 10 numbers and end
       .forEach 
           // do work on the final list
       

这些功能的组合往往会消除对continuebreak 的需求。这里有无穷无尽的不同选择,而且比可以记录的还要多。要了解可以做什么,最好了解 Kotlin 标准库中针对 collections、lazy sequences 和 iterable 的所有可用函数。

有时在某些情况下,您的变异状态仍需要breakcontinue,并且在功能模型中很难做到。您可以使用更复杂的函数(如 foldreduce 结合 filtertakeWhile 函数)使其工作,但有时这更难理解。因此,如果您真的想要那种确切的行为,您可以使用 return from lambda expression,它根据您的使用情况模仿 continuebreak

这是一个模仿continue的例子:

(1..5).forEach  
    if (it == 3) return@forEach  // mimic continue@forEach
    // ... do something more

当您遇到嵌套或混乱的情况时,您可以变得更复杂并使用标签:

(1..3).forEach outer@  x ->
    (1..3).forEach inner@  y ->
        if (x == 2 && y == 2) return@outer // mimic continue@outer
        if (x == 1 && y == 1) return@inner // mimic continue@inner
        // ... do something more
    

如果你想做一个break,你需要一些可以从循环之外返回的东西,这里我们将使用run()函数来帮助我们:

run breaker@ 
    (1..20).forEach  x ->
        if (x == 5) return@breaker  // mimic break@forEach
        // ... do something more
    

而不是run(),它可以是let()apply() 或任何您想从forEach 周围自然而然出现的东西。但是您也会跳过forEach 之后的同一块中的代码,所以要小心。

这些是内联函数,所以实际上它们并没有真正增加开销。

阅读 Returns and Jumps 的 Kotlin 参考文档,了解包括匿名函数在内的所有特殊情况。


这是一个单元测试,证明这一切正常:

@Test fun testSo32540947() 
    val results = arrayListOf<Pair<Int,Int>>()
    (1..3).forEach outer@  x ->
        (1..3).forEach inner@  y ->
            if (x == 2 && y == 2) return@outer // continue @outer
            if (x == 1 && y == 1) return@inner // continue @inner
            results.add(Pair(x,y))
        
    

    assertEquals(listOf(Pair(1,2), Pair(1,3), Pair(2,1), Pair(3,1), Pair(3,2), Pair(3,3)), results)

    val results2 = arrayListOf<Int>()
    run breaker@ 
        (1..20).forEach  x ->
            if (x == 5) return@breaker
            results2.add(x)
        
    

    assertEquals(listOf(1,2,3,4), results2)

【讨论】:

使用带标签的中断和返回是代码异味(恕我直言)。请参阅下面的@user8320224 的答案以获得更优雅和惯用的功能解决方案。 @Mark 我将答案调整为为此功能模型和字面答案的组合。 @store88 扩展了答案以提供更多选项。 不幸的是,过滤器给了我一个新列表,所以如果我需要改变某些东西并且@breaker 需要在它之外标记一些东西,所以它的情境 forEachIndexed?【参考方案2】:

takeWhile stdlib 函数可以用来代替 break。

例如,

val array = arrayOf(2, 8, 4, 5, 13, 12, 16)
array.takeWhile  it % 2 == 0 .forEach  println(it)  // break on odd
array.takeWhile  it % 3 != 0 .forEach  println(it)  // break on 3 * n

【讨论】:

一个使用示例会改善你的答案。 请注意,这会将所有令人满意的元素复制到新分配的中间集合中。 当处理序列而不是(或从)数组创建时,上述对中间集合的关注是无关紧要的(因为序列是惰性的并且不构建中间集合)。恕我直言,这是一个比公认的更好的答案,您只需要在序列上使用它。 序列并不总是更快,取决于列表大小和所采取的操作。这已被反复证明。 @JaysonMinard 如果您的评论是写给我的(似乎是这样),请注意我并没有说序列会更快——只是使用序列会消除之前提到的关于中间集合。【参考方案3】:

带break的forEach可以具体替换为anyfunction:

(1..20).any  x ->
    (x == 5).apply  // break on true
        if (!this) 
            results2.add(x)
        
    

或者甚至更短:

(1..20).any  x ->
    results2.add(x)
    x == 4 // break on true

【讨论】:

这是对any函数的完全滥用,该函数旨在仅查找序列中是否存在条件,如果存在则返回真/假,尽早结束。它不是为处理而设计的,也不能在功能链的中间使用,而只能在末端使用。

以上是关于在 Kotlin 中的功能循环中,如何“中断”或“继续”?的主要内容,如果未能解决你的问题,请参考以下文章

Kotlin笔记--break

如何停止或摆脱 Twig 循环?

Kotlin 学习之被我一直用错的“return@forEachIndexed/return@forEach”

Kotlin 学习之被我一直用错的“return@forEachIndexed/return@forEach”

Kotlin 学习之被我一直用错的“return@forEachIndexed/return@forEach”

使用Kotlin中的foreach循环遍历BigInteger值