首页 文章

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

提问于
浏览
32

在Kotlin中,我不能在函数循环和我的lambda中做 breakcontinue - 就像我可以从正常的 for 循环中做的那样 . 例如,这不起作用:

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

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

Note: 这个问题是由作者故意撰写和回答的(Self-Answered Questions),因此对于常见的Kotlin主题的惯用答案存在于SO中 . 还要澄清为Kotlin的alphas写的一些非常古老的答案,这些答案对于当前的Kotlin来说是不准确的 .

4 回答

  • -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 sequencesiterable .

    有时候有些情况下你需要变异状态仍然需要 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 的东西,这是一个你想要打破的地方 . 但是你也会在_1258489之后跳过同一个区块内的代码,所以要小心 .

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

    阅读所有特殊情况(包括匿名函数)的Returns and Jumps的Kotlin参考文档 .


    Here is a unit test 证明这一切都有效:

    @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)
    }
    
  • 69

    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
    
  • 0

    forEach with break可以特别用any function代替:

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

    如果需要使用 continuebreak ,那么使用 forEach 比较正常 for-loop 是不理想的

    如果你真的想链接你的命令,并像for循环那样执行,那么使用正常的功能链,而不是 forLoop

    例如 . 对于

    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 }
    

相关问题