Javaのcontinueとは?ループを次に続けるための使い方【for/while文を制御しよう】
Javaのcontinueは、for文やwhile文などのループ文で「今のループを終わらせて、次のループを始める」ために使うものです。continueはbreakと並んで、Javaでのループをコントロールするための重要な機能の一つです。
continueは機能としては大変シンプルなものです。でも、例えばfor文との組わせでは、for文が動く仕組みをきちんと理解していないと「え、どういうこと?」となりがちです。さらにwhile文でもちょっとしたワナが待っています。
プログラムでは、for文やwhile文がいくつも重なりあってしまって、とても「深く」なることがあります。その場合のcontinueはなかなか大変なのですが、そのような時に使える機能もcontinueにはちゃんと備わっています。
この記事では、continueの基本から少し発展した話題まで、初心者向けに幅広く扱います。
※この記事のサンプルは、Java 11の環境で動作確認しています
1.continueとはどういうものか
1-1.continueとは次のループをすぐ始めるもの
continueは今のループをすぐ終わらせて、次のループを始めるための構文です。continueはループ文、つまりfor文/while文/do-while文の中にしか書けません。ループを制御することが目的の構文だからです。
continueは、言い換えれば「もうこのループではもうやることがないから、次のループに行くよ」ということです。そのため、やることがないと判断するif文やswitch文との組み合わせが普通です。もちろん、ループの最後でcontinueしてもいいのですが、あまり意味があることではありません。
なお、continueを実行することを「continueする」ともプログラマはよく言います。この記事でも以後はその表現を使いますので、慣れておくといいでしょう。
1-2.continueの書き方
continueの書き方はとてもシンプルで、ループ文中のどこかで以下のように書くだけです。
continue;
continueにはこのように何もパラメータを指定しないのが普通です。でも、実はcontinueにはラベルと呼ばれるものをパラメータにできます。
ただ、ラベル付きのcontinueは、そうそう使うものではありません。私自身も実務のJavaプログラムでは使ったほとんど覚えがありません。でも、知っておくのは無駄にはなりません。そんなパラメータ付きのcontinueは、別の章でお伝えします。
1-3.for文/拡張for文でのcontinueの動き
for文は、以下のように分解できます。continueは、for文の中では③の処理部でならどこにでも書けて、またcontinueすると④継続式が実行されます。
for (①初期化式; ②条件式; ④継続式) { ③処理部 }
では、実際にfor文でのcontinueを試してみましょう。iが1のループだけが途中で終わって、次のループが行われていることが分かるでしょうか。
System.out.println("for文の前です"); for (int i = 0; i < 3; i++) { System.out.println("今は" + i + "です"); if (i == 1) { System.out.println("continueします"); continue; } System.out.println(i + "の処理の終わりです"); } System.out.println("for文の後です");
実行結果
for文の前です 今は0です 0の処理の終わりです 今は1です continueします 今は2です 2の処理の終わりです for文の後です
continueの動きをもう少しわかりやすくするために、for文の各部分でprintしてみます。continueをすると、その次に動くのは②条件式ではなく、④継続式ですよね。これでcontinueするとどこに処理が移るか、具体的にお分かりいただけたでしょうか。
class ContinueSample { static int init() { System.out.println("①初期化式です"); return 0; } static boolean condition(int i) { System.out.println("②条件式です: " + i + " < 3"); return i < 3; } static void next(int i) { System.out.println("④継続式です: i = " + i); } public static void main(String[] args) { System.out.println("for文の前です"); for (int i = init(); condition(i); next(i), i++) { System.out.println("③処理部です、今は" + i + "です"); if (i == 1) { System.out.println("continueします"); continue; } System.out.println(i + "の処理の終わりです"); } System.out.println("for文の後です"); } }
実行結果
for文の前です ①初期化式です ②条件式です: 0 < 3 ③処理部です、今は0です 0の処理の終わりです ④継続式です: i = 0 ②条件式です: 1 < 3 ③処理部です、今は1です continueします ④継続式です: i = 1 ②条件式です: 2 < 3 ③処理部です、今は2です 2の処理の終わりです ④継続式です: i = 2 ②条件式です: 3 < 3 for文の後です
拡張for文なら、あまり難しいことは考えなくてもOKです。ここでも、continueは③処理部にならどこにでも書けます。
for (①変数宣言部; ②対象指定部) { ③処理部 }
こちらもプログラム例を示します。
int[] array = { 0, 1, 2 }; System.out.println("拡張for文の前です"); for (int i : array) { System.out.println("③処理部です、今は" + i + "です"); if (i == 1) { System.out.println("continueします"); continue; } System.out.println(i + "の処理の終わりです"); } System.out.println("拡張for文の後です");
実行結果
拡張for文の前です ③処理部です、今は0です 0の処理の終わりです ③処理部です、今は1です continueします ③処理部です、今は2です 2の処理の終わりです 拡張for文の後です
1-4.while文/do-while文でのcontinueの動き
while文/do-while文でも、continueの使い方はfor文と同じです。ループをしているところに書けて、continueすると条件式に処理が移ります。
int i = 0; System.out.println("while文の前です"); while (i < 3) { System.out.println("今は" + i + "です"); if (i == 1) { System.out.println("continueします"); i++; continue; } System.out.println(i + "の処理の終わりです"); i++; } System.out.println("while文の後です");
実行結果
while文の前です 今は0です 0の処理の終わりです 今は1です continueします 今は2です 2の処理の終わりです while文の後です
int i = 0; System.out.println("do-while文の前です"); do { System.out.println("今は" + i + "です"); if (i == 1) { System.out.println("continueします"); i++; continue; } System.out.println(i + "の処理の終わりです"); i++; } while (i < 3); System.out.println("do-while文の後です");
実行結果
do-while文の前です 今は0です 0の処理の終わりです 今は1です continueします 今は2です 2の処理の終わりです do-while文の後です
1-4-1.【参考】while文ではカウンター変数の加減算のし忘れに気を付けよう!!
前述した例でcontinueの前にカウンター変数iを加算しているのは、while/do-while文のcontinueでは地味に重要です。カウンター変数を加減算し忘れるのは、continueを使う上では大変よく起きるバグです。恥ずかしながら、私もやったことがあります。
カウンター変数とcontinueを使うなら、for文との組み合わせの方がより確実かもしれません。while文ではfor文と違って、カウンター変数の加算処理などのループ終了後の共通処理を確実にできるところがありませんので、continueの前にカウンター変数を自分で+1しておく必要があるのです。
単純なカウンターなら、以下のように条件式で行うことも普通です。前++(前置)と後ろ++(後値)のどちらにすべきかは、状況によります。ただし、この場合だと、ループ中でiを直接参照する時は気を付けましょう。前置・後値のいずれを使うにせよ、条件式が終わった時点で、既に+1されているからです。
while (i++ > 2) {
1-5.ネストしたループ文でのcontinueにはご注意を!!
for/while/do-while文はネスト(nest、入れ子、何かの中に何かを含むこと)させられます。ループ分がネストした状態でcontinueすると、continueしたところから「最も近い」ループが次のループになります。
例えば以下では、continueしているところから「上へ」たどっていって、もっとも近いループ文である「jのfor文」をcontinueします。ですから「iのfor文」はそのまま何事もなかったかのように続くのです。
System.out.println("ネストしたfor文を実行します"); for (int i = 0; i < 2; i++) { System.out.println("今は i = " + i + "です"); for (int j = 0; j < 3; j++) { System.out.println(" 今は j = " + j + "です"); if (j == 1) { System.out.println(" continueします。jのfor文は次のループへ行きます"); continue; } System.out.println(" j = " + j + "のループが終わりました"); } System.out.println("i = " + i + "のループが終わりました"); } System.out.println("ネストしたfor文を抜けました");
実行結果
ネストしたfor文を実行します 今は i = 0です 今は j = 0です j = 0のループが終わりました 今は j = 1です continueします。jのfor文は次のループへ行きます 今は j = 2です j = 2のループが終わりました i = 0のループが終わりました 今は i = 1です 今は j = 0です j = 0のループが終わりました 今は j = 1です continueします。jのfor文は次のループへ行きます 今は j = 2です j = 2のループが終わりました i = 1のループが終わりました ネストしたfor文を抜けました
ですので、ループがネストした状態だと、意図したcontinueが難しくなりることがあります。親ループでcontinueできることが、ネストが深いところにあるループ文で分かった場合は面倒です。その場合は、ループ全体の継続要否を管理する変数を使ったりすることになります。
そういうプログラムは複雑になりがちです。ですが、そういう用途に使えるよう、いくらループが深くなっても親のループを子ループから直接continueするやり方があるのでご安心ください。それが前述したラベル付きcontinueです。
1-6.breakとの違いはループそのものを抜けるか
continueは今のループを中断して次のループを始めます。breakは今のループを中断してループ文そのものを終了します。ですので、breakは「もうループ文そのものをもう終わらせてもいいよ」と判断した時に使うので、ある意味ではcontinueよりも「強い」ものです。
System.out.println("for文の前です"); for (int i = 0; i < 3; i++) { System.out.println("今は" + i + "です"); if (i == 1) { System.out.println("breakします"); break; } System.out.println(i + "の処理の終わりです"); } System.out.println("for文の後です");
実行結果
for文の前です 今は0です 0の処理の終わりです 今は1です breakします for文の後です
breakの詳細については、以下の記事も参照してください。
関連記事2.【発展】for/while文のラベル付きcontinue
2-1.ラベルとループ文とcontinueの関係
ラベルとは、for文/while文/do-while文の前に付けられる、ループ文の名前のようなものです。そしてcontinueは、実はそのラベル付けしたループ文へ実行できるのです。
前章では「continueしたところから最も近いループ文が対象になる」と書きましたが、これはcontinueする対象をプログラマが指定しない場合の、デフォルトの動きです。
この章では、ラベル付きcontinueについて、ラベルのつけ方からスタートしつつ、簡単に触れてみます。
2-2.ラベルは文字列 + “:”でつける
ラベルは以下のように「ラベル文字列 + “:”」という形式で書きます。ラベル文字列に使える文字は変数名と同じですが、ラベルであることが分かりやすくするように大文字とすることが普通です。
ラベル文字列:
2-3.ループ文にラベルを付ける
for文/while文/do-while文へラベル付けするには、以下のようにそれぞれのキーワードの「前」にラベルを置きます。
LABEL: for(int i = 0; i < 10; i++) { // ループ文の中身 } LABEL: while(true) { // ループ文の中身 } LABEL: do { // ループ文の中身 } while(true);
もちろん、ラベル付けしたループ文がたくさんネストしていても大丈夫です。それぞれのループ文に、ラベル付けがされます。
LABEL1: for (int i = 0; i < 2; i++) { // ここはLABEL1の中 LABEL2: for (int j = 0; j < 2; j++) { // ここはLABEL2の中 LABEL3: for (int k = 0; k < 2; k++) { // ここはLABEL3の中 } } }
ただし、同じラベルのループ文の中では、既に使われているラベルを使えません。ループ文の区別が付けられないからで、この考え方は変数のスコープと似ています。
ですが、外にある違うループ文同士なら同じラベルでも問題ありません。なぜかと言えば、ラベルの有効範囲は紐づけたループ文の中だけだからです。
LABEL: for (int i = 0; i < 2; i++) { LABEL: for (int j = 0; j < 2; j++) { // コンパイルエラー!! ラベル付けされたループ文の中では、同じラベルは使えない } } LABEL: for (int k = 0; k < 2; k++) { // 外にある違うループ文へなら同じラベルが使える }
2-4.ラベルを使ったcontinue
いよいよラベル付きループ文をcontinueしてみます。以下のように、continueの後ろにcontinueしたいラベル文字列を指定しましょう。
continue ラベル文字列;
System.out.println("ラベル付きfor文に入ります"); LABEL1: for (int i = 0; i < 2; i++) { System.out.println("今は i = " + i + "です"); LABEL2: for (int j = 0; j < 3; j++) { System.out.println(" 今は j = " + j + "です"); if (j == 1) { System.out.println(" LABEL1をcontinueします"); continue LABEL1; } System.out.println(" j = " + j + "の終わりです"); } System.out.println("i = " + i + "の終わりです"); } System.out.println("ラベル付きfor文から抜けました");
実行結果
ラベル付きfor文に入ります 今は i = 0です 今は j = 0です j = 0の終わりです 今は j = 1です LABEL1をcontinueします 今は i = 1です 今は j = 0です j = 0の終わりです 今は j = 1です LABEL1をcontinueします ラベル付きfor文から抜けました
実行結果を見ると、jのループの中から、iのループを直接continueできていることが分かると思います。ですから、ループ文のネストが深くなっても、ラベルさえつけておけば、直接「上」にあるループ文をcontinueできるのですね。
3.contiueといろいろ
3-1.continueとtry-finallyは少し注意しよう
continueとtry-finallyの組み合わせは、少しだけ注意しましょう。tryの中でcontinueすると、セットのfinallyも実行されます。finallyを差し置いて、すぐに次のループに行くわけではないのですね。ですから「これは実行されるはずじゃなかったのに…」ということがないようにしましょう。
for (int i = 0; i < 3; i++) { System.out.println("今は" + i + "です"); if (i == 1) { try { System.out.println("continueします"); continue; } finally { System.out.println("continueでのfinallyです"); } } System.out.println(i + "の処理の終わりです"); }
実行結果
今は0です 0の処理の終わりです 今は1です continueします continueでのfinallyです 今は2です 2の処理の終わりです
3-2.Streamでcontinueしたい時はreturnが近い意味
Java 8から追加されたStream APIはループ文に似た処理ができます。ですが、あくまで「ループに似たもの」であり、構文上のループ文ではないのでcontinueはできません。
List<String> list = Arrays.asList("A", "B", "C"); list.stream().forEach(s -> { continue; // ←コンパイルエラー!! for/whileのようなループ文の中ではないため、continueは行えない });
Streamは、正確に言えば何かの集まりにある要素一つ一つに対して処理を並行に行うための仕組みであって、ループ的に何かをするわけではないからです。ですので、要素一つに対する処理をreturnで中断することはできますが、Stream全体を中断することはループ文と同じようにはできないのです。
ですので、ループ中でcontinue的な動きをする必要があるなら、従来どおりfor/while文を使うことになります。また、近い動きにしたいなら、Stream上で今のデータの処理をreturnで終わらせてしまえば、continueに近い動きになります。
List<String> list = Arrays.asList("A", "B", "C"); list.stream().forEach(s -> { // 引数のStringが"B"なら、処理をせずにreturnする。 // 次のループを行うcontinueと、比較的近い動きにはなる。 if ("B".equals(s)) { return; } System.out.println(s); });
3-3.ループではなるべく早いタイミングでcontinueすべき
ループ中でcontinueする時は、できるならなるべく早いタイミングで行うのがいいと考えます。returnには早期returnという考え方がありますが、continueやbreakにも同じようなことが言えます。
ループ中ではcontinueできるなら早くしてしまえば、そこから後ろの処理ではcontinueする条件以外なのだと明確にできます。結果として、ループ文などの全体的な見通しが良くなります。
今のプログラムでcontinueをしている箇所は、本当にそこで行わなければならないのか考えてみましょう。もっとシンプルにできるかもしれません。ただ、早くcontinueをしたいがために自然なロジックを捻じ曲げるべきではありませんので、バランス感覚が大事です。
for (int i = 0; i < 10; i++) { if (continueできる条件) { continue; } // ここから後ろはcontinueできる条件以外だと確定する } for (int i = 0; i < 10; i++) { if (何かの条件1) { if (何かの条件2) { } } else if (何かの条件3) { // 本当にここでなければcontinueができないのか? if (continueできる条件) { continue; } } }
4.まとめ
この記事では、Javaにおけるcontinueを紹介してきました。continueはfor/while文の中で実行でき、今のループを終了させて次のループとするためのものです。深くネストしたループ文からは、ラベル付きcontinueをすると、親のループ文をcontinueできます。
continueとwhile文の組み合わせで、カウンター変数を使っている場合は注意しましょう。うっかりカウンター変数の加減算を忘れてしまうことがあります。結構見つけづらいバグになることがありますので、変な動きをしている場合は疑ってみてもいいでしょう。
continueはシンプルな機能ですから、他にはあまり注意するところはありません。でも、continueはプログラムを作っていれば日常的に使うものであるからこそ、分かりやすく、読みやすい方法で行いたいものです。何気ないcontinueでも、考えなしに使ってしまうと、結構わかりづらくなってしまうものですよ。
コメント