Java : for文の基本 (文法)
for文は、プログラムを繰り返し処理するための制御文です。
while文 と比べると、繰り返し回数を変数で制御したいときに向いています。
本記事では、Java の for文のうち、基本for文と呼ばれる文法について解説します。
もう1つの for文である 拡張for文については、下記の記事でまとめています。
そちらもぜひ参考にしてみてくださいね。
※本記事では、単に "for文" と書いているときは "基本for文" のことをさします。
基本for文
基本for文は、プログラムを繰り返し処理させるための制御文です。
for (初期化処理; 条件式; 更新処理) {
... ループ文 ...
}
これが基本の形となります。
コード例で見てみましょう。
for (int i = 0; i < 5; i++) {
System.out.println("i = " + i);
}
// 結果
// ↓
//i = 0
//i = 1
//i = 2
//i = 3
//i = 4
for文が開始されると、まず 初期化処理 が実行されます。
int i = 0
この部分ですね。
変数 i を宣言して 0 で初期化しています。
次に 条件式 がチェックされます。
そして、条件式が true の間は、ループ文と更新処理を繰り返し実行します。
条件式は
i < 5
の部分です。
i 変数は 0 で初期化したので、条件式は最初 0 < 5 となり、結果は true ですね。
よって、ループ文 が実行されます。
ループ文は
System.out.println("i = " + i);
の部分です。
※System.out.println は、値をコンソールに出力するためのメソッドです。
ループ文の実行が終わると、更新処理 が実行されます。
更新処理は、
i++
の部分です。
i 変数に 1 を加算します。
ここまで終わると、あとは 条件式 のチェックに戻って繰り返します。
(初期化処理 は最初の1回だけ実行されます)
i 変数は、繰り返しのたびに 0 → 1 → 2 → 3 ... と増えていきます。
そして、i が 5 になった次の条件式のチェックは 5 < 5 となり、結果は false で for文が終わります。
コンソールには、
i = 0
i = 1
i = 2
i = 3
i = 4
と出力されます。
もう一度、処理の流れをまとめてみましょう。
for (int i = 0; i < 5; i++) {
System.out.println("i = " + i);
}
- for文開始
- 初期化処理を実行。
- 条件式をチェック。true であればループ文を実行。false であれば for文は終了。
- ループ文を実行。
- 更新処理を実行。
- 条件式のチェックに戻る。
- 初期化処理を実行。
- for文終了
フローチャート図で書くと次のようになります。
それでは、もう少し詳しく見ていきましょう。
ノート
- for文のカウンタ変数の名前には、よく i が使われます。なぜ i なのでしょうか?
数学の変数名で使われているから、最初期のプログラミング言語である FORTRAN の整数型の変数宣言が I だから、などなど所説あるようです。 - ともあれ、このような慣習には従っておいたほうがよいでしょう。
なぜなら、他のプログラマが i を見たときに「あ、この変数は for文のためのカウンタなんだな」とすぐに理解できるからです。さらに、変数が1文字なのでコードもすっきりしますしね。
- 参考:language agnostic - Why are variables "i" and "j" used for counters? - Stack Overflow
初期化処理
for (初期化処理; ; ) {
... ループ文 ...
}
初期化処理は、for文が開始された直後に1回だけ実行されます。
主な用途としては、カウンタ変数の宣言および初期化です。
for (int i = 0; ...; ...) {
...
}
上記の例では、変数 i を宣言して 0 で初期化しています。
変数宣言以外では、文 や、クラスのメソッド呼び出しなども書くことができます。
int num = 0;
// コンパイルOK
for (num += 10; ; ) {
...
}
// コンパイルOK
for (System.out.println("abc"); ; ) {
...
}
しかし、式 は書けません。コンパイルエラーになります。
int num = 5;
// コンパイルエラー
for (num + 5; ; ) {
...
}
// コンパイルエラー
for (num == 5; ; ) {
...
}
初期化処理で宣言された変数は、for文の内側のみで使えます。
for (int i = 0; i < 5; i++) {
// コンパイルOK
System.out.println("i = " + i);
}
for文の外側では使えません。スコープ外となります。
for (int i = 0; i < 5; i++) {
...
}
// コンパイルエラー
System.out.println("i = " + i);
もし初期化処理が不要なときは、空 にすることもできます。
// 意図的に、変数 i の宣言を初期化処理の外側にします。
int i = 0;
for (; i < 5; i++) {
}
// コンパイルOK
System.out.println("i = " + i);
条件式
for (; 条件式; ) {
... ループ文 ...
}
条件式は、結果が boolean(真偽値) となる 式 のみ許容します。
boolean にならないものはコンパイルエラーとなります。
例えば < や > による大小の比較や、== による比較は、結果が boolean となる式です。
// 式 : 5 > 3
final var exp1 = 5 > 3;
System.out.println(exp1); // true
// 式 : 4 < -1
final var exp2 = 4 < -1;
System.out.println(exp2); // false
// 式 : 10 == 10
final var exp3 = 10 == 10;
System.out.println(exp3); // true
int num = 0;
// コンパイルOK
for (; num > 3; ) {
...
}
// コンパイルOK
for (; num < -1; ) {
...
}
// コンパイルOK
for (; num == 10; ) {
...
}
もしくは、結果が boolean となるメソッドの呼び出しも OK です。
例えば、文字列の比較に使う equals メソッドは boolean を返します。
final var exp1 = "abc".equals("abc");
System.out.println(exp1); // true
final var exp2 = "abc".equals("xyz");
System.out.println(exp2); // false
var str = "abc";
// コンパイルOK
for (; str.equals("xyz"); ) {
...
}
複数の条件式を使いたい場合は && や || といった論理演算も使えます。
final var exp1 = 5 > 3;
System.out.println(exp1); // true
final var exp2 = "abc".equals("xyz");
System.out.println(exp2); // false
final var exp3 = exp1 && exp2;
System.out.println(exp3); // false
final var exp4 = exp1 || exp2;
System.out.println(exp4); // true
var num = 5;
var str = "abc";
// コンパイルOK
for (; num > 3 && str.equals("xyz"); ) {
...
}
// コンパイルOK
for (; num > 3 || str.equals("xyz"); ) {
...
}
足し算、引き算など + や - を使った式は、結果が数値となります。
boolean ではないので for文の条件式には使えません。
// 式 : 5 + 3
final var exp1 = 5 + 3;
System.out.println(exp1); // 8
// 式 : 4 - 1
final var exp2 = 4 - 1;
System.out.println(exp2); // 3
var num = 5;
// コンパイルエラー
for (; num + 3; ) {
...
}
// コンパイルエラー
for (; num - 1; ) {
...
}
条件式は省略することができます。
その場合は、true を直接指定したときと同じになります。
// 条件式を省略。
for (; ; ) {
...
}
// こう書くのと同じです。
for (; true; ) {
...
}
補足
- Java以外のプログラミング言語には、条件式に boolean 以外を許容しているものもあります。
例えば C言語では、条件式に数値も許容しています。 0 は false、それ以外は true として扱います。 - しかし、Java では厳密に boolean のみを許容します。0 や 1 といった数値を条件式に使うとコンパイルエラーになります。
更新処理
for (; ; 更新処理) {
... ループ文 ...
}
更新処理は、ループ文が終わるたびに実行されます。
主な用途としては、カウンタ変数の更新です。
for (...; ...; i++) {
...
}
上記の例では、ループ文が終わるたびに、変数 i を +1 します。
更新処理では、文 やクラスのメソッド呼び出しなどを書くことができます。
int num = 0;
// コンパイルOK
for (; ; num += 10) {
...
}
// コンパイルOK
for (; ; System.out.println("abc")) {
...
}
式 は書けません。コンパイルエラーになります。
int num = 5;
// コンパイルエラー
for (; ; num + 5) {
...
}
// コンパイルエラー
for (; ; num == 5) {
...
}
初期化処理には書けた変数の宣言もできません。
// コンパイルエラー
for (; ; int i = 0) {
...
}
もし更新処理が不要なときは、空 にすることもできます。
for (int i = 0; i < 5; ) {
i++;
System.out.println("i = " + i);
}
// 結果
// ↓
//i = 1
//i = 2
//i = 3
//i = 4
//i = 5
break文
for (; ; ) {
... ループ文 ...
if (...) {
break;
}
}
さて、今までのコード例では、for文を終わらせるには条件式を false にする必要がありました。
しかし、条件式以外の任意の箇所で for文から抜けたい (終わらせたい) ことがあるかもしれません。
そんなときに使えるのが break文です。
break文は、ループ文の中で次のように書きます。
break;
コード例で見てみましょう。
for (int i = 0; ; i++) {
System.out.println("------");
System.out.println("loop : start");
System.out.println(" i = " + i);
if (i == 2) {
System.out.println("Break!");
break;
}
System.out.println("loop : end");
}
// 結果
// ↓
//------
//loop : start
// i = 0
//loop : end
//------
//loop : start
// i = 1
//loop : end
//------
//loop : start
// i = 2
//Break!
今回は、for文を break文で終わらせたいので、条件式は省略しています。
これで、条件式では for文は終わらなくなりました。
for (int i = 0; ; i++) {
...
}
ループ文が繰り返されるたびに、更新処理で i 変数を 1 ずつ加算します。
そして、i == 2 になったときに "Break!" と出力して break文 で for文を抜けます。
System.out.println("------");
System.out.println("loop : start");
System.out.println(" i = " + i);
if (i == 2) {
System.out.println("Break!");
break;
}
System.out.println("loop : end");
結果は次のように出力されます。
------
loop : start
i = 0
loop : end
------
loop : start
i = 1
loop : end
------
loop : start
i = 2
Break!
ここで注目したいのが、i = 2 のときのループ処理では、最後の "loop : end" が出力されない ことです。
break文で for文を抜けると、break以降の処理は実行されません。
continue文
for (; ; ) {
... ループ文 ...
if (...) {
continue;
}
}
continue文を使うと、ループ文の処理をそこでやめて for文の更新処理へジャンプする、という制御ができます。
continue文は、ループ文の中で次のように書きます。
continue;
文章では少しわかりにくいかもしれません。
コード例で見てみましょう。
for (int i = 0; i < 4; i++) {
System.out.println("------");
System.out.println("loop : start");
System.out.println(" i = " + i);
if (i % 2 == 1) {
System.out.println("Continue!");
continue;
}
System.out.println("loop : end");
}
// 結果
// ↓
//------
//loop : start
// i = 0
//loop : end
//------
//loop : start
// i = 1
//Continue!
//------
//loop : start
// i = 2
//loop : end
//------
//loop : start
// i = 3
//Continue!
条件式は i < 4 です。
for (int i = 0; i < 4; i++) {
...
}
ループ文が繰り返されるたびに、更新処理で i 変数を 1 ずつ加算します。
そして、i が奇数のとき (i % 2 == 1) は "Continue!" と出力して continue文 を実行します。
System.out.println("------");
System.out.println("loop : start");
System.out.println(" i = " + i);
if (i % 2 == 1) {
System.out.println("Continue!");
continue;
}
System.out.println("loop : end");
結果は次のように出力されます。
------
loop : start
i = 0
loop : end
------
loop : start
i = 1
Continue!
------
loop : start
i = 2
loop : end
------
loop : start
i = 3
Continue!
i が偶数のときは continue文は実行されません。
そのため、ループ処理の最初で "loop : start" と出力され、ループ処理の最後で "loop : end" と出力されます。
i が奇数のときは continue文が実行されます。
continue文が実行されると、すぐに for文の更新処理 (i++) へジャンプします。"loop : end" は出力されません。
無限ループに注意
基本for文を使うときは、無限ループに注意しましょう。
もし無限ループが起きてしまうと、不具合の症状の1例としては、アプリは反応がなくなりフリーズしてしまいます。
アプリのクラッシュと並んで、致命的なバグの1つですね。
次のコード例を見てみましょう。
for (double num = 1.0; num > 0.0; num *= 10) {
System.out.println(num);
}
num は浮動小数点数(double)です。
num が 0 より大きい間、ループを続けます。
そして、更新処理では num を 10倍していきます。
出力は次のようになります。
10.0
100.0
1000.0
10000.0
100000.0
1000000.0
... 省略 ...
9.999999999999999E306
9.999999999999998E307
Infinity
Infinity
Infinity
... 省略 ...
num の値が、10 → 100 → 1000 → 10000 ... と増えているのがわかりますね。
さらに続けていくと、最終的には正の無限大(Infinity) となります。
Infinity は、そこからいくら 10倍しても Infinity のままです。
そして Infinity は 0 より大きいので、この for文は永遠に実行し続けることになります。
これが無限ループです。
基本for文を使うときは、条件式や break文で、きちんとループが終了するかを常に気にかけなければなりません。
これがなかなか神経を使うのですよね…
もし 拡張for文 (for-eachループ文) が使えるケースであれば、そちらを使うことをおすすめします。
拡張for文は、なんと条件式を使わずにループ処理が可能です。(その代わり使えるケースは限定的です)
{ } の省略
for (初期化処理; 条件式; 更新処理)
ループ文
ループ文が 1つの文 のみの場合は { } を省略することができます。
// 通常の書きかた
for (int i = 0; i < 3; i++) {
System.out.println("i = " + i);
}
// こう書いたり…
for (int i = 0; i < 3; i++)
System.out.println("i = " + i);
// こう書くこともできます。
for (int i = 0; i < 3; i++) System.out.println("i = " + i);
ただし、{ } を省略すると、思わぬコーディングミスを誘発してしまうかもしれません。
次のコード例を見てみましょう。
var num = 0;
for (; num < 3; ) System.out.println("num = " + num); num++;
さて、ぱっと見て、どのような結果になるかわかりますでしょうか。
実は、この for文は無限ループします。
コンソールには
num = 0
num = 0
num = 0
num = 0
num = 0
...
と、"num = 0" が出力され続けます。
なぜこのような結果になるのか考えてみましょう。
for文の { } を省略したので、ループ文は
System.out.println(num);
の 1つの文 だけが対象となります。
その後ろの
num++;
の文は、for文の外側となります。
つまり次のコードと同じ意味になります。
var num = 0;
for (; num < 3; ) {
System.out.println("num = " + num);
}
num++;
num 変数は for文で変わることはないので、いつまでも条件式は false にならず無限ループしてしまう…というわけでした。
{ } がないと、どこまでが ループ文なのか分かりにくくなることがあります。
個人的には、1つの文のみの場合でも { } は省略しないほうがよいんじゃないかな…と思います。
代替えとなる方法
Java言語には、for文の代わりとなるものがいくつか用意されています。
ここではそれらを簡単にご紹介します。
while文
初期化処理と更新処理が不要なときは while文が使えます。
while (条件式) {
... ループ文 ...
}
これが基本の形となります。
処理の流れは次のようになります。
- while文開始
- 条件式を判定。true であればループ文を実行。false であれば while文は終了。
- ループ文を実行。
- 条件式の判定に戻る。
- while文終了
コード例です。
{
int i = 0;
while (i < 3) {
System.out.println("i = " + i);
i++;
}
}
// 結果
// ↓
//i = 0
//i = 1
//i = 2
これは for文を使って次のように書くのと同じです。
for (int i = 0; i < 3; i++) {
System.out.println("i = " + i);
}
// 結果
// ↓
//i = 0
//i = 1
//i = 2
while文については、よろしければ下記の記事もご参照ください。
拡張for文
for (T value : "配列" か "Iterable" を実装したクラス) {
...
}
拡張for文は、配列 もしくは Iterable を実装したオブジェクトに対して、繰り返し処理を行います。
例えば List は Iterable を実装しています。
条件式は必要とせず、すべての要素に対して繰り返し処理が実行できます。
Iterable は基本有限なので、無限ループの心配はほぼありません。 (実装次第では無限にもできますが…)
List の要素に順次アクセスして出力する例です。
final var list = List.of("aaa", "bbb", "ccc");
for (final var value : list) {
System.out.println("value = " + value);
}
// 結果
// ↓
//value = aaa
//value = bbb
//value = ccc
これは 基本for文を使って次のように記述するのと同じです。
final var list = List.of("aaa", "bbb", "ccc");
for (final var it = list.iterator(); it.hasNext(); ) {
final var value = it.next();
System.out.println("value = " + value);
}
// 結果
// ↓
//value = aaa
//value = bbb
//value = ccc
拡張for文については、よろしければ下記の記事もご参照ください。
Stream API
拡張for文をさらに発展させたものとして、Stream API があります。
こちらは少し難易度が高めなので、Javaに慣れてからでよいとは思います。
Stream API を使って List の要素を大文字に変換してから出力する例です。
final var list = List.of("aaa", "bbb", "ccc");
list.stream().map(String::toUpperCase).forEach(value -> {
System.out.println("value = " + value);
});
// 結果
// ↓
//value = AAA
//value = BBB
//value = CCC
Stream API については下記の記事で詳しく解説していますので、興味がありましたらご参照ください。
公式ドキュメント
さらに詳しく 基本for文について知りたい場合は、公式のJava言語仕様に記載があります。
(残念ながら公式の日本語訳はないようです)
まとめ
基本for文を使うと、プログラムを繰り返し処理させることができます。
特に、繰り返し回数を変数で管理したいときに便利です。
ただし、場合によっては 基本for文より優れた方法もあります。
例えば、拡張for文 や Stream API です。
もしそれらが使える状況であれば、ぜひそちらを使うことも検討してみましょう。
関連記事
- ストリームの基本 (Stream)
- if文の基本
- while文の基本
- プリミティブ型 (基本データ型)
- リテラルの表記方法いろいろ
- クラスの必要最低限を学ぶ
- インタフェースの default メソッドとは
- インタフェースの static メソッドの使いどころ
- var (型推論) のガイドライン
- アクセス修飾子の基本
- 配列 (Array) の使い方
- 拡張for文 (for-eachループ文)
- switch文ではなくswitch式を使おう
- try-with-resources文でリソースを自動的に解放
- テキストブロックの基本
- 列挙型 (enum) の基本
- ラムダ式の基本
- レコードクラスの基本 (Record Class)
- メソッド参照の基本
- シールクラスの基本(Sealed Class)
- 無名変数の使い方