KotlinでforEachの使い方がわからない
forとforEachの違いがわからない
forEachのループからうまく抜け出せない
KotlinでforEachを書こうと思っても基本的な使い方がわからなかったり、詳しくわかっておらずなぜかループから抜け出せれないという人はいませんか?
こんにちは!エンジニアのかいです。Kotlinをこれから始めようとしている人、もしくは始めたての人であればプログラミングの基本中の基本であるforEachについて上記のような悩みを抱えている人も多いのではないでしょうか。
そこで本日はKotlin初心者の方を対象にforEachの基本的な使い方を説明していきます。基本的な使い方に加えループの抜け出し方や、そのときに使うlabelと概念についても説明していきます。
この記事はこんな人のために書きました。
- KotlinをはじめたばかりなのでforEachの書き方、使い方がわからない
- forとforEachの使い方がわからない
- forEachのループからうまく抜け出せない
- labelの使い方がよくわからない
KotlinのforEachとは
さて、実際に使い方を紹介して行く前にそもそもKotlinのforEachとはなんなのかという基本的な内容をおさらいしておきましょう。forEachとはリストの要素を一つずつ取り出して処理を行うループ処理のためのものです。
例えば配列の数値の全てに1を足したい場合や、配列の中に特定の文字列が存在するかなどを確認するときに使うこともできますね。言葉で説明するよりも実際のコードを見た方がわかりやすいと思うので紹介していきます。
基本的な使い方をみてみよう
fun main() { val numbers = arrayOf(1, 2, 3, 4, 5) numbers.forEach{ i -> println(i) } numbers.forEach{ println(it) } }
1 2 3 4 5 1 2 3 4 5
いきなりコードを書きましたが、KotlinではforEachでは上記のように書きます。ここで注目していただきたのは、1回目のforEachではこのようにiを持たせていますが、2回目では持たせていませんね。
Kotlinではこのように、明示的に変数に格納もできれば、暗黙的にitという変数に格納することもできます。つまり、書かなかったらitに代入されるということですね。
少し詳しい話をすると、->という記法はラムダ式といい、ラムダ式は(メソッドの引数) -> {処理}という風に書くことができます。そして、その引数が一つの場合はitに置き換えることができるようにKotlinではなっているんですね。
forEachから抜け出す方法
次はこちらの例をみてください。
fun main() { (1..10).forEach myloop@ { println(it) if (it == 7) { return@myloop } } println("done") }
このコード、結果はどのようになると思いますか?答えは以下の通りです。
1 2 3 4 5 6 7 8 9 10 done
7までで表示したあと、doneが出て終わると思った方も多いのではないでしょうか。これがKotlin始めたての方がよくしてしまう間違えやすいポイントです。
labelの意味を知ろう
この原因を解明するための前提条件をしっかりと理解しておきます。まずmyloop@ってなんだ?と思った方もいるかもしれませんが、これはlabelと呼ばれるものです。
labelとは、Kotlinにおける任意の式をマークできるもので、この場合では、forEach文をマークしているということですね。そしてretun@myoopでforEachから抜けようとしています。
ちなみにforEachなどの単純式ではこのように明示的にlabelをつけることはあまりなく、以下のような暗黙のラベルといって、もともと決まっているラベルを使う方が便利です。
fun main() { (1..10).forEach{ println(it) if (it == 7) { return@forEach } } }
1 2 3 4 5 6 7 8 9 10 done
とはいえ、結果は同じですね。
ジャンプ演算子の種類
次にジャンプ演算子の種類を確認しておきます。Kotlinには構文をジャンプするための演算子はreturn, break, continueの3が用意されており、定義は以下のとおりです。
return: 関数を終わらせる
break: 最も近いループを終わらせる
continue: 最も近いループの外側のループの次のステップに進む
forEachではbreakやcontinueは使えない
ちなみにこの説明を受けて、なぜ上のコードではbreakなどを使わずにlabelを使っていたかという疑問が出る人もいるかもしれませんが、結論からいえば、forEachではbreakやcontinueは使えないので同様の処理はreturnやlabelを使って行う必要があるからです。
なぜfor文では使えてforEachでは使えないかという点に関しては、forは構文で、forEachは関数だからです。もっとわかりやすくいえば、forは構文としてbreakやcontinueが設計されていますが、forEachは標準ライブラリでサポートされた関数なのです。
関数の中でbreakなんてみたことないですよね。上記でも説明した通り関数を終わらせる演算子はreturnだけです。
なぜ思い通りの挙動にならなかったのか
さて、ここまできて「1から7が表示されてdoneが表示される」というのが思い通りの挙動としたときになぜそうならなかったか考えてみましょう。
fun main() { (1..10).forEach{ println(it) if (it == 7) { return@forEach } } println("done") }
このコードではreturn@forEachに実質、breakを期待していますよね。forEachをreturnで終わらせているのでそのように動く気もします。
ですがこれは実際には、return@forEachはcontinueであるためこのような挙動になっているのですね。では、どのようにかけばbreakとして機能してくれるのでしょうか。
fun main() { run { (1..10).forEach { println(it) if (it == 7) { return@run } } } println("done") }
答えはこのようにrunという関数で囲ってあげてrun関数をreturnしてやれば良いです。
1 2 3 4 5 6 7 done
まとめ
今日はforEachの使い方を学んできました。
forEachでかけることは全てforで書き換えることができ、forの方が早いので推奨という声もありますが、両方の使い方や違い、はまりポイントをしっかり理解しておくことも今後必要になってくるのでfor文しか知らないのではなく、forEach文もしっかりと理解しておきましょう。
for文に関するリンクは以下にはっておきますね。
それでは!!