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

在Kotlin中,我无法在函数循环和lambda中执行continuebreak,就像在普通for循环中所做的那样。 例如,这不起作用:

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

有旧的文档提到此功能可用,但似乎从未实现。 当我想从lambda内部获取continuebreak时,获得相同行为的最佳方法是什么?

注意:这个问题是作者故意写和回答的(自我回答的问题),因此SO常见问Kotlin主题的惯用答案。 还要澄清一些针对Kotlin字母的非常古老的答案,这些答案对于当今的Kotlin而言并不准确。

4个解决方案
92 votes

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

您可以避免使用run()处理某些值:(例如run()

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

您可以使用run()来停止功能循环:(例如run()

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
       }

这些功能的组合往往消除了对run()let()的需求。这里有无数种不同的选择,而且还有很多无法记录的选择。 要想知道可以做什么,最好是学习Kotlin标准库中用于集合,延迟序列和可迭代的所有功能。

有时在某些情况下,您仍然需要run()let()的突变状态,并且在功能模型中很难做到。 您可以使用更复杂的功能(例如apply()forEach)以及forEachtakeWhile功能来使其工作,但有时很难理解。 因此,如果您确实想要这种确切的行为,则可以使用lambda表达式的返回值来模拟continuebreak,具体取决于您的用法。

这是一个模仿run()的示例:

(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
    }
}

如果您想执行run(),则需要在循环之外进行返回的操作,这里我们将使用let()函数来帮助我们:

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

代替run()的可能是let()apply(),或者自然而然地围绕着forEach的任何事物,您都想从这里来。 但是您也将在forEach之后的同一块中跳过代码,因此请小心。

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

阅读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)
}
Jayson Minard answered 2020-07-30T18:05:29Z
1 votes

可以使用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
user8320224 answered 2020-07-30T18:05:53Z
0 votes

带有break的forEach可以具体替换为任何函数:

(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
}
Vadzim answered 2020-07-30T18:06:17Z
-1 votes

如果需要使用mapfilterNot,则与普通for-loop相比,使用asSequence() & first并不理想

如果您真的希望链接命令并像执行for循环那样执行操作,请使用常规功能链,而不是map

例如。 对于

    for(i in 0 until 100 step 3) {
        if (i == 6) continue
        if (i == 60) break
        println(i)
    }

使用map作为循环,使用filterNot作为循环,使用asSequence() & first作为中断

    (0 until 100 step 3).asSequence()
            .filterNot { it == 6 }
            .map { println(it); it }
            .first { it == 60 }
Elye answered 2020-07-30T18:06:51Z
translate from https://stackoverflow.com:/questions/34642868/how-do-i-do-a-break-or-continue-when-in-a-functional-loop-within-kotlin