web-dev-qa-db-ja.com

Kotlin内の機能ループ内で「ブレーク」または「継続」を行うにはどうすればよいですか?

Kotlinでは、通常のbreakループから実行できるように、関数ループとラムダ内でcontinueまたはforを実行できません。たとえば、これは機能しません:

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

古いドキュメント があり、これが利用可能であることを述べていますが、実装されていないようです。ラムダ内からcontinueまたはbreakにしたいときに同じ振る舞いを得る最良の方法は何ですか?

注:この質問は作者によって意図的に書かれ、回答されています(- Self-Answered Questions ) 、そのため、よく聞かれるKotlinのトピックに対する慣用的な回答がSOにあります。また、現在のKotlinでは正確ではないKotlinのアルファ用に書かれたいくつかの本当に古い回答を明確にするため。

43
Jayson Minard

同様の機能を提供する、あなたが求めているもの以外の他のオプションがあります。例えば:

filter :(like a continue)を使用すると、一部の値の処理を回避できます。

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

takeWhile :(like a 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
       }
_

これらの関数の組み合わせは、continueまたはbreakの必要性を排除する傾向があります。そして、ここには無限のさまざまなオプションがあり、文書化できる以上のものがあります。何ができるかを知るには、 コレクション 、遅延 シーケンス 、および 反復可能 のKotlin標準ライブラリで使用可能なすべての関数を学習するのが最適です。

場合によっては、breakまたはcontinueを必要とする変化状態があり、機能モデルでは実行が難しい場合があります。 foldおよびreduceのようなより複雑な関数をfilterおよびtakeWhile関数と組み合わせて使用​​することで機能させることができますが、時にはそれを理解するのが難しくなります。したがって、その正確な動作が本当に必要な場合は、 ラムダ式からの戻りを使用できます 使用法に応じてcontinueまたはbreakを模倣します。

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に続く同じブロック内のコードもスキップするため、注意してください。

これらはインライン関数なので、実際にはオーバーヘッドを追加しません。

リターンとジャンプ 匿名関数を含むすべての特殊なケースについては、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)
}
_
90
Jayson Minard

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
1
user8320224

forEach with breakは、具体的に 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
}
0
Vadzim