Javaのforとは?繰り返し処理を行うための構文「for文」の使い方
for文は、Javaで繰り返し処理を行うための構文です。for文は、while文と並び、Javaの繰り返し構文の代表格です。
for文は他のプログラミング言語にもある普遍的なものですので、Javaで基本をしっかり覚えれば、他のプログラミング言語でも簡単に応用できます。
この記事では、for文について初心者向けに基本からお伝えします。より便利で簡単に扱える拡張for文にも触れますので、ぜひ覚えていってください!
※この記事のサンプルは、Java 10の環境で動作確認しています
目次
1.for文が動く仕組み
1-1.for文の実行順序を理解しよう
Javaのfor文は以下の部分から作られています。最も重要なのは、各部分がどのタイミングでどういう順序で実行されるかです。それさえ理解すれば、for文はわかったも同然です!!
for (①初期化式; ②条件式; ④反復式) { // ③処理部 }
for文は「①初期化式→②条件式→③処理部→④反復式→②条件式→③処理部→④反復式→…」の順序で実行されます。これは確実に覚えましょう。どれだけ複雑なfor文でも、この流れは絶対に守られます。
①の初期化式は、for文が実行される際に「1回だけ」実行されます。普通は、ここでカウンター変数の宣言・初期化などを行います。
②の条件式は、繰り返しを行おうとするタイミングで「毎回必ず実行」されます。この式の評価結果がtrueなら③の処理部が実行され、falseなら即座にfor文が終了します。
③の処理部は、②の条件式がtrueになった時にだけ実行されます。for文の本体です。
④の反復式は、③の処理が終わった時には必ず呼び出されます。普通はカウンター用変数の操作が行われます。この後は②の条件式が実行されます。
そして必ず押さえておくべきポイントは、②の条件式がtrue/falseのどちらになるかで、繰り返しが続くかどうか決まるということです。この部分の間違いが、実務のプログラムでもとても多いものなのです。
1-2.for文の動きを見てみよう
では、簡単な例でfor文の動きを見てみましょう。2回繰り返しを行い、その時点でのカウンター変数(i)の値をprintするfor文です。
for (int i = 0; i < 2; i++) { System.out.println(i); }
このfor文の要素と動きを全て書き下すと、以下のようになります。前述した「①初期化式→②条件式→③処理→④反復式→②条件式→…」の結果により、2回printされることが分かると思います。
①初期化式 → int i = 0
②条件式 → i < 2
③処理部 → System.out.println(i)
④反復式 → i++
初期化式の実行
1: int i = 0 → int型の変数iを初期値0で宣言
1回目のループ
2: 0 < 2 → この時点ではiは0、「0 < 2」の結果を判断、結果はtrueなので処理部を実行
3: System.out.println(0) → この時点のiの値(0)をprint
4: 0++ → この時点でiは0、++されると1になる
2回目のループ
5: 1 < 2 → この時点ではiは1なので、「1 < 2」の結果を判断、結果はtrueなので処理部を実行
6: System.out.println(1) → この時点のiの値(i)をprint
7: 1++ → この時点でiは0、++されると2になる
3回目のループ
8: 2 < 2 → この時点ではiは2、「2 < 2」の結果を判断、結果はfalseとなり、for文は終了する
2.初期化式は変数の初期化に用いる
初期化式は、文字通りfor文を実行するのに必要な諸々の初期化を行う部分です。
普通は、for文の中で用いるカウンター変数を宣言するのに使います。でも、for文の使い方によっては、別の型の変数を宣言することも普通ですし、変数の宣言ではなく何かの処理を実行することもあります。
// 初期化式でint型の変数iを宣言 for (int i = 0; i < 10; i++) { }
初期化式では1つの文しか書けません。ですが、Javaでは , で区切れば1つの文で同じ型なら複数の変数を宣言できますので、以下のようにもできます。
// 初期化式でint型の変数iとjを宣言し、初期値も設定する for (int i = 0, j = 1; i < 10; i++) { }
初期化式は省略できます。for文の中だけで使いたい変数がない場合は、省略しても問題ありません。ただし、; は必要です。
int i = 0; for (; i < 10; i++) { // 初期化式は省略できる、でも ; は必要!! }
※プログラミング言語でのカウンター変数は、i、j、k…とアルファベット順に付けていくのが慣習です。これは慣習というだけで、変数名はもちろん自由に使えます。ですが、i、j、k…はカウンター変数だと多くのプログラマーが認識することは知っておきましょう。
2-1.初期化式で宣言した変数のスコープ
初期化式で宣言した変数は、そのfor文の中からしか参照できません。for文の前で同名の変数が宣言されていても駄目で、コンパイルエラーになります。
for (int i = 0; i < 10; i++) { // 何かの処理 } System.out.println(i); // ← コンパイルエラー!! 初期化式で宣言したiはfor文の中でしか使えない。
int i = 123; for (int i = 0; i < 10; i++) { // ← コンパイルエラー!! このスコープで宣言済みの変数は初期化式で宣言できない // 何かの処理 }
ですので、for文での処理結果を後続の処理で使いたい場合は、for文の前で変数を宣言しておかなければなりません。
int v = 0; // for文の外で変数を宣言して… for (int i = 0; i < 10; i++) { v += i; // for文の中で値を更新し… } System.out.println(v); // for文の外で使う!!
3.条件式は必ずbooleanを戻す
Javaではfor文の条件式は、必ずbooleanとならなければなりません。booleanでなければコンパイルエラーになります。この厳密にbooleanの結果を求める条件式が、Javaのfor文でキモとなる特徴です。
int[] array = { 1, 2, 3 }; for (int i = 0; array.length; i++) { // コンパイルエラー、array.lengthはintであり、booleanではないため } for (int i = 0; i < array.length; i++) { // OK、i < array.lengthは比較演算なのでbooleanを必ず戻す }
一方、他のプログラミング言語のfor文だと、boolean以外も使えます。C言語では0はfalse扱い、0以外はtrue扱いです。JavaScriptでは“”、0、undefined、nullなど色々なものがfalse扱いされます。でも、繰り返しますがJavaでは必ずbooleanでなければなりません。
なお、条件式は最終的にbooleanになればいいので、いくつかの条件を組み合わせても大丈夫ですし、メソッドの戻り値でも問題ありません。if文の条件式部分と同じように書けると考えてもらっても構いません。
boolean flag = true; for (int i = 0; i < 10 && flag; i++) { // 何かの条件でflagがfalseになることがあるかも!! } List list = Arrays.asList(1, 2, 3); Iterator it = list.iterator(); for (; it.hasNext();) { }
条件式も省略できます。条件式を省略すると常にtrue扱いとなり、何もしないとfor文が終わりません(いわゆる無限ループ)。無限ループを終わらせるために、後述するbreakを処理部のどこかで行わなければなりません。
for (int i = 0;; i++) { // 条件式が省略されているのでfor文は終わらない→無限ループ!! }
4.処理部には繰り返したい処理を書く
処理部は繰り返したい処理本体を書く部分です。典型的な例ではカウンター変数を使って配列やListの要素にアクセスし、何かの処理を行います。後述するbreakやcontinueは処理部の中で行うものです。
処理部には、少なくとも1つの文(;で終わるもの)あるいはブロック({}で囲うもの)が必要です。
// for文の対象を1つの文のみとした場合 for (int i = 0; i < 10; i++) System.out.println(i);
なお、実行したいのが1つの文だけだとしても、常にブロックにすることをお勧めします。文だと前後の繋がりが分かりづらくなり、気付かない内にバグを作り込む原因ともなるからです。
for (int i = 0; i < 10; i++) System.out.println(i); System.out.println(i + 1); // ←コンパイルエラー!! この文はfor文の対象外なのでiを参照できない /* // ↑はこう書いているのと同じなので、iはfor文最初の文でしか使えない for (int i = 0; i < 10; i++) { System.out.println(i); } System.out.println(i + 1); */
5.反復式では繰り返し後に行いたい処理を書く
反復式は、繰り返し処理の最後に必ず実行される部分です。それを利用して、繰り返しごとに必ず行う処理をここに書きます。典型的なものは、カウンターの増減です。
for (int i = 0; i < 10; i++) { // 繰り返し時には必ずiが+1される System.out.println(i); }
ここも一つの式しか書けないのですが、初期化式と同様に , で区切ることはできるので、複数のカウンターの管理も行えます。
for (int i = 0, j = 10; i < 10; i++, j--) { // iとjの両方を増減させている System.out.println(i); }
反復式も記述は省略できます。カウンターではなく、メソッドの結果で条件式を作る場合には、反復式は書かないことも普通です。
List list = Arrays.asList(1, 2, 3); for (Iterator it = list.iterator(); it.hasNext();) { // 反復式を省略している
6.フロー制御の構文
6-1.continueは処理部を中断して次を繰り返す
continueは、処理部を中断して反復式に処理を移します。continue後は反復式→条件式の順に実行されます。「この条件の場合は処理をしたくないけれど、繰り返し自体は続けたい」という時に使います。
for (int i = 0; i < 10; i++) { if (i % 2 == 0) { // 偶数の場合は、iをprintせずにすぐ次のループへ行く System.out.println("continue"); continue; } System.out.println(i); }
6-2.breakは処理部を中断してfor文を抜ける
breakは、処理部を中断してfor文を抜けます。そのループ分の反復式も実行されません。「この条件を満たせばこのfor文は終わってもいい」という時に使います。
for (int i = 0; i < 1000; i++) { if (i > 500) { // iが500を超えたなら、for文を抜ける break; } System.out.println(i); }
for文で無限ループをする場合は、処理部のどこかでbreakしないとプログラムが終わらないことに注意しましょう。
7.入れ子になったfor文
for文は必要なだけ入れ子にできます。これは多次元配列を扱う場合などに相性が良いものです。
実行イメージとしては、外側のfor文による繰り返しの1回ごとに、内側のfor文全体が実行されるというものです。以下の例での、カウンター変数iとjの動きに注目しましょう。iは0→1→2…と順番に変わりますが、jはiが+1されるごとに0→1→2→3→4を繰り返します。
for (int i = 0; i < 5; i++) { System.out.println("i:" + i); for (int j = 0; j < 5; j++) { System.out.println("j:" + j); } }
入れ子になったfor文で実行したcontinueやbreakは、最も近いfor文に適用されます。
for (int i = 0; i < 5; i++) { for (int j = 0; j < 5; j++) { break; // →int j = 0のfor文にのみbreakが適用される } } for (int i = 0; i < 5; i++) { for (int j = 0; j < 5; j++) { continue; // →int j = 0のfor文にのみcontinueが適用される } }
ラベルを用いると、指定したラベルに対応するfor文へのbreak/continueになります。ラベルを使ったbreak/continueを使うことはほとんどないと思いますが、できるということは頭の片隅に覚えておくと良いでしょう。もしかすると、役に立つ時があるかもしれません。
// OUTER1がラベル OUTER1: for (int i = 0; i < 5; i++) { for (int j = 0; j < 10; j++) { break OUTER1; // →int i = 0のfor文にbreakが適用される } } // OUTER2がラベル OUTER2: for (int i = 0; i < 5; i++) { for (int j = 0; j < 10; j++) { continue OUTER2; // →int i = 0のfor文にcontinueが適用される } }
8.for文の使い方の例
ここまでがfor文の基本です。ここではfor文の使い方の例をいくつか示します。
8-1.決まった回数の実行
最も基本となるfor文は、特定回数の繰り返しです。例えば何かを10回繰り返したいなら以下のようにします。
// カウンター変数をプラスしながら10回実行する for (int i = 0; i < 10; i++) { System.out.println(i); }
プログラミングの初心者が混乱しやすいのは、Javaの世界では0始まりが普通だということです。これは慣れておきましょう。カウンター変数が0なら1回目、1なら2回目…9なら10回目なので、10になった時点でfor文を終わらせなければなりません。
もちろん、カウンター変数は0から始まらなければ駄目なのではありません。1から始めてもいいですし、逆に減らしていってもOKです。以下の例では、カウンター変数が1なら10回目なので、0になった時点でfor文を終わらせます。10回繰り返すという意味では同じものです。
// カウンター変数をマイナスしながら10回実行する for (int i = 10; i > 0; i--) { System.out.println(i); }
反復式を工夫すれば、偶数・奇数のみとできたりもします。
// iを1つ飛ばしにする→カウンターが偶数だけになり、5回実行される for (int i = 0; i < 10; i += 2) { System.out.println(i); }
8-1-1.無限ループ
条件式に何も書かなければ無限ループになります。一見使い道がなさそうですが、実は実務のプログラミングでは結構使います。いわゆるサーバのプログラミングでは、外部からの入力を待ったり、決まった時刻まで待つための無限ループがどこかにあるものです。
for (;;) { // どこかでbreakしないかぎりずっと動き続ける }
例えば、以下は一定時間(この例では1秒)待ち、別スレッドで行われる何かの処理が終わっていればbreakするというものです。
boolean finished = false; // 処理継続判断のためのbooleanがどこかで宣言されていて… for (;;) { // 無限ループをしながらひたすら待って… try { Thread.sleep(1000); } catch (InterruptedException e) { } if (finished) { // どこかで別の処理がfinishedをtrueにしたら終了 break; } }
ちなみに、無限ループの発生は、PCやサーバのCPU使用率を見ていると分かることがあります。プログラムは特に何も処理をしていなさそうなのに、CPU使用率が100%のまま…という場合は意図しない無限ループが発生しているかもしれません。
8-2.配列へのアクセス
8-2-1.一次元配列へのアクセス
配列の要素にfor文で全てアクセスするのは、for文の用途として最も一般的なものです。
配列のlengthは配列の要素数を表しますので、条件式での全部の要素を見終わったかの判断に使用します。配列のインデックスは0始まりですので、最後の要素は 配列名[length – 1] です。条件式でlengthを使う際は、< を用いるのが良く見かけるスタイルです。
String[] array = { "A", "B", "C", "D", "E" }; for (int i = 0; i < array.length; i++) { System.out.println(array[i]); } // 条件式をこのように書いても意味は同じですが、↑の方が多数派かも? // 条件式が実行される度に計算が発生するのもデメリットです。 for (int i = 0; i <= array.length - 1; i++) { System.out.println(array[i]); }
カウンター変数を加減算すれば、for文の中で配列要素の先読み、後読みができます。当然ながら、配列に存在しないインデックスにアクセスすると例外が発生しますので、if文でガードするなどしましょう。
String[] array = { "A", "B", "C", "D", "E" }; for (int i = 0; i < array.length; i++) { if (i > 0) { System.out.println("1つ前は" + array[i - 1]); } System.out.println(array[i]); if (i < array.length - 1) { System.out.println("1つ後は" + array[i + 1]); } }
8-2-2.多次元配列へのアクセス
多次元配列の各要素へのアクセスは、入れ子のfor文で表現することが普通です。その際は、カウンター変数の管理をしっかり行いましょう。多次元配列のカウンター変数の管理が、最もバグを作ってしまいがちな部分です。
String[][] array = { { "A", "B", "C", "D", "E" }, { "あ", "い", "う", "え", "お" } }; for (int i = 0; i < array.length; i++) { for (int j = 0; j < array[i].length; j++) { System.out.println(array[i][j]); } }
ちなみに、配列のインデックスを超えたアクセスをしないためには、lengthを参照するのが最も安全です。プログラムの仕様で「必ずlengthはXXXになる」と決められていることは多いと思いますが、チェックをするに越したことはありません。
8-3.Listへのアクセス
8-3-1.size/getでのアクセス
Listにおいて、配列のlengthに相当するものは、List内の要素数を戻すメソッドsizeの戻り値です。Listではインデックスを使った要素の取出しはget(int)を使います。
List<String> list = Arrays.asList("あ", "い", "う", "え", "お"); for (int i = 0; i < list.size(); i++) { System.out.println(list.get(i)); }
見てのとおり、配列へのアクセスとイメージは変わらないのが分かると思います。
8-3-2.Iteratorでのアクセス
インデックスによるListの操作はバグを生む可能性があります。Listを先頭から順にアクセスすればいいのなら、Iteratorを使うと安全に繰り返しを書けます。
List<String> list = Arrays.asList("あ", "い", "う", "え", "お"); for (Iterator it = list.iterator(); it.hasNext();) { System.out.println(it.next()); }
これは、Iteratorの変数it(Iteratorの略)をfor文内のローカル変数としていることがポイントです。前述のとおり、初期化式には式を書けるので、カウンター変数の宣言以外にも使えます。
初期化式を使わず、以下のようにも書けますが、この場合は変数itはfor文の外でも使えるローカル変数になります。変数のスコープを局所化できていると、変数の誤用を防止できますので、積極的に使っていきたいですね。
List list = Arrays.asList("あ", "い", "う", "え", "お"); Iterator it = list.iterator(); for (; it.hasNext();) { Object o = it.next(); System.out.println(o); }
9.拡張for文
9-1.拡張for文の書き方
Java 1.5で追加された拡張for文によるforeachを用いると、配列やコレクションの全要素へ先頭から簡単にアクセスできます。しかも、通常のfor文で必要な、インデックスや配列・コレクションなどからの要素の取り出し処理を自分で書かなくてもいいのです。
ちなみに、拡張for文は、通常のfor文で行っていることを簡略化して書けるようになっただけです。つまり、for文でやるような処理をJavaが裏で勝手にやってくれているだけです。これを構文糖衣(syntax sugar)と言ったりします。
// 拡張for文の構文 for (取り出す要素の変数宣言 : 配列あるいはjava.lang.Iterableのインスタンス) { 繰り返し処理; }
なお、通常のfor文では()の中での区切りが“;”(セミコロン)二つですが、拡張for文では“:”(コロン)一つだけなので間違えないようにしましょう。
for文と比較した場合の制限は、必ず配列・コレクションの先頭からの処理となることです。後ろから処理をしたい場合は、拡張for文では直接対応できませんので、あらかじめ逆順にソートし直すなどの処置が必要です。一つ飛ばしなどにしたい場合も、ひと工夫必要です。
9-2.拡張for文の例
例としては以下のとおりです。配列とListを用いてみました。
// 配列での例 int[] array = {1, 2, 3}; for (int e : array) { // eはElement(要素)のEです。以下のサンプルで全て同じです。 System.out.println(e); // 1, 2, 3と順番に出力される }
// Iterableでの例(ここではList) List<Integer> list = Arrays.asList(1, 2, 3); for (Integer e : list) { System.out.println(e); // 1, 2, 3と順番に出力される }
Listの例では、List内に保持している型をジェネリクスの型変数(List<Integer>のInteger)で指定しています。ですので、拡張for文での変数の型もIntegerとなります。
取り出す変数の型は、配列なら配列で保持している要素の型です。ということは、多次元配列を拡張for文で扱う場合の変数は、Javaでは多次元配列は「一次元目の配列の要素には、二次元目の配列への参照を保持している」のですから、二次元目の配列となるのです。
// 二次元配列での例 int[][] array = {{1, 2, 3}, {4, 5, 6}}; for (int[] subarray : array) { // 一次元目の配列から二次元目の配列への参照を取り出して、 for (int e : subarray) { // さらに二次元目の配列でforeachすると、 System.out.println(e); // 1, 2, 3, 4, 5, 6と順番に出力される } }
10.まとめ
for文は初期化式、条件式、処理部、反復式から作られています。for文の動きを理解するには、これらが必ず決まった順番で実行されることをまず理解しましょう。
Javaのfor文が特徴的なのは、条件式がbooleanの結果を戻さなければならないということです。他言語(C言語、JavaScript等)のfor文とはここが最も異なるポイントなので、コンパイルエラーが出る時は必ずチェックしましょう。
for文はJavaにおける繰り返し処理の基本です。ですが、Javaでの繰り返し処理はfor文の他にも拡張forループ、while文、Stream APIなどがありますので、用途・状況に応じて使い分けられるようになりましょう。
コメント