14
10

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Kotlin: なぜ while, for では break が使えて forEacn, repeat では使えないのか

Last updated at Posted at 2018-12-30

Kotlin の while, for では break, continue が使えますが、forEach, repeat では使えません。
forEach, repeat は いずれも for で書き換えられるので、書き換えてしまえば break, continue が使えます。

  • something.forEach => for (it in something)
  • repeat (time) => for (it in 0..time - 1)

なぜこうなっているのか気になりますね。
(コルーチンの内部だと書き換えられない場合もあります。)

なぜなのか

(現実をいってしまえば Kotlin がそう作られているからなのですが、)
while, forforEach, repeat には大きな違いがあります。

  • while, for は Kotlin の構文
  • forEach, repeat は Kotlin の関数

似た記法になっているのでわかりにくくなっていますが、 while, for は構文として、 break, continue が使えるように設計されています。
一方で、 forEach, repeat は Kotlin の標準ライブラリで定められた高階関数です。

repeat (3) {
    // this is function
}

つまり { ... } は関数です。
関数の中で急に breakcontinue が出てくるのは変ですbreak, continue は使えません。

イテレーションをコントロールする他の方法はないのか

forEach, repeat などの高階関数で、 ブロック部分を抜けるには、 return を使います。
関数ですから return で終了するのは自然に思えますね。

しかし、 return をそのまま使うと、 呼び出し元の関数を終了してしまいます。

fun something() {
    listOf(1, 2, 3).forEach {
        println(it)
        return // something を終了する
    }
    println("finish")
}

そこで、ラベルを使います。

continue のような処理

ラムダを使う

return@ラムダのラベル でラムダの continue を行うことができます。

fun something() {
    listOf(1, 2, 3).forEach {
        println(it)
        return@forEach  // ブロック内の処理はここまで
        println(it)
    }
    println("finished")
}

このラベルは自分でつけることもできて ラベル名@{ ... } のように記述します。

もし forEach が二重だったら

二重で forEach がある場合は、ラベルを自分でつけます。
次のコードは外側のループに対して continue を行なっています。

Kotlin-lambda
fun something() {
    listOf(1, 2, 3).forEach outer@{ a ->
        listOf(4, 5, 6).forEach { b ->
            print(a)
            return@outer
            println(b)
        }
    }
    println("finished")
}

無名関数を使う

ラムダを無名関数に変えれば、 単純に return で同じことができます。

Kotlin-anonymousfunction
fun something() {
    listOf(1, 2, 3).forEach(fun(value: Int) {
        println(value)
        return          // ブロック内の処理はここまで
        println(it)
    })
}

jQuery でも似たようなコードを見たことがあります。

JavaScript
$("xxx").each(function  () {
  /* Code */
  if (x) return;
  /* Code */
});

break のような処理

ラムダを使う

外側をスコープ関数の run で囲んで、 そのラムダを抜けるように return@run と記述します。

Kotlin-lambda
fun something() {
    run {
        listOf(1, 2, 3).forEach {
            println(it)
            return@run  // ループはここでおわり
            println(it)
        }
    }
    println("finished")
}

見てわかるように、 高階関数は break を前提に作られていません。

無名関数を使う

ラムダと同じように run などのスコープ関数を使って return@run とします。

Kotlin-anonymousfunction
fun something() {
    run {
        listOf(1, 2, 3).forEach(fun() {
            println(it)
            return@run  // ループはここでおわり
            println(it)
        })
    }
    println("finished")
}

まとめ

上に記述したのをまとめると次のようになります。

break continue
ラムダ label label
無名関数 flag and return, label return
14
10
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
14
10

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?