【Java入門】第16回 繰り返し処理(1)|for文の利用と配列・コレクション
2024.07.12
前回の記事(第15回 配列とコレクション)で複数のデータを様々な形で管理する方法をご説明しましたが、複数のデータを1つのオブジェクトにまとめる目的として、それらのデータに対して一律の処理を行うことが挙げられます。
当記事では、配列やコレクションに格納したデータに対して処理を繰り返す、基本的な方法についてご説明します。
目次
繰り返し処理
実際に利用されているプログラムでは、Javaに限らず配列、コレクションなど複数のデータを持つものに対して、繰り返し同じ処理を行うことが多くあります。
この同じ処理を繰り返し行うことを「ループ処理」「処理のループ」などと呼びます。
ここでは、繰り返し処理の方法としてもっとも一般的な、for文を利用した処理について説明します。
for文
基本的な構文
for文では、以下の3つの式を利用して、繰り返し処理を制御します。
・繰り返し処理を行う前の初期処理(初期化式)
・次のループを行うかどうかの条件(条件式)
・ループごとに行う後処理(変化式)
実行条件や処理は、以下の形式で記述します。
for( 初期化式 ; 条件式 ; 変化式 ) { 繰り返し実行したい処理 }
以下の例では、ループが何周目かをカウントするための変数 i を準備し、0 から 9 まで出力します。
/**
* for文の動作確認クラス
*/
public class ForMain {
/**
* mainメソッド
* @param args
*/
public static void main(String[] args) {
// for文を利用した繰り返し処理を実行
for ( int i = 0; i < 10; i++) {
System.out.println(i);
}
System.out.println("--forループを終了--");
}
}
・初期化式
for文の開始時に1回だけ実行する処理を記述します。
(ループ用のカウンタとして使う変数の宣言や、値の初期化などを記述することが多い)
条件式の条件に合致せず、1周目の処理が行われずに(1周もループせずに)終わった場合でも、初期化式に記述した処理は実行されることに注意してください。
・条件式
for文での繰り返し処理を行うかどうかの条件を記述します。
ループ1周ごとに毎回条件式を評価し、条件を満たさなくなった場合は次のループは実行せず、for文の次の処理へ移動します。
・変化式
for文のループごとに、後処理として実行する処理を記述します。
(ループのカウンタ用の変数のインクリメントを記述することが多い)
・繰り返し実行する処理
繰り返し実行したい処理(処理の本体)を記述します。
基本的にfor文の直後にブロックを記述し、ブロック内に処理を記述します。
※if文と同様に、実行する処理はブロックを省略して記述することも可能ですが、ループの範囲がわかりにくくなるため、基本的にはブロックを付けて記述するようにしましょう。
(ブロックを省略した場合は、次の1処理(次の「 ; 」までの処理)を繰り返し実行します)
for文に記述した処理は、以下の① → ②の流れで実行されます。
① 最初に初期化式の処理を実行する。
【ループの前の処理】
・初期化式(int i = 0)で、for文の中の処理で利用できる変数「i」を宣言し、0を代入して初期化する。
② 条件式を満たす限り、for文の次のブロックの処理を繰り返す。
+
ブロックの処理が完了するごとに、変化式の処理を実行する。
【ループ処理の流れ】
【ループ処理】
1周目(最初のループ)
・i = 0 なので、 条件式( i < 10)を満たすため for文のブロック内の処理を実行し、 i の値(0)を出力する。
・for文のブロック内の処理を完了したら、変化式の処理(i++)が実行され、 i の値が0 → 1 に変化。
2周目
・i = 1 なので、 for文のブロック内の処理を実行。( i の値(1)を出力)
・処理を完了したら、変化式の処理を実行。( i の値:1 → 2)
★中略(3~9周目)★ ループの繰り返しで、2~8までを順次出力
10周目(「8」を出力した次のループ)
・i = 9 なので、 for文のブロック内の処理を実行。( i の値(9)を出力)
・処理を完了したら、変化式の処理を実行。( i の値:9 → 10)
11周目
・i = 10 となり、 条件式( i < 10)を満たさないため、ループを終了。
・for文のブロックをスキップし、後続の処理を実行。
実行結果:
0
1
2
3
4
5
6
7
8
9
--forループを終了--
条件式の設定の誤りや、変化式の記述ミス(インクリメントする変数を間違った、など)があると、ループ処理が終了せずにいつまでも実行される(いわゆる無限ループになる)こともありますので、初期化式、条件式、変化式の組み合わせが意図したものであるか、かならず確認するようにしましょう。
複数の初期化式/条件式/変化式
初期化式、変化式には、複数の処理を記述することも可能です。
また条件式には、演算子を用いることで複数条件を指定することも可能です。
・初期化式
複数の変数を宣言する、または、複数の処理を記述する、のどちらかが可能です。
処理は値の代入、メソッドの実行などが記述できますが、if条件のような複雑な処理は記述できません。
for文の中で使用する変数の宣言と、他の処理を同時に行うことはできません。
複数の変数宣言、または処理を記述する場合は、「,」(カンマ)で区切って記述します。
(1)複数の変数を宣言
以下の例では、i、j の2つの変数を初期化式の中で宣言しています。
複数の変数を宣言する場合は、同じ型の変数のみ宣言することが可能です。
// for文の中で使う変数 i, j を宣言
for(int i = 0, j = 0; i < 10; i++) {
if(i % 2 == 0) {
j++; // 偶数のときだけカウント(偶数の個数を保持)
}
System.out.println("今回の値:" + i + ", ここまでの偶数の数: " + j + "個");
}
今回の値:0, ここまでの偶数の数: 1個
今回の値:1, ここまでの偶数の数: 1個
今回の値:2, ここまでの偶数の数: 2個
今回の値:3, ここまでの偶数の数: 2個
今回の値:4, ここまでの偶数の数: 3個
今回の値:5, ここまでの偶数の数: 3個
今回の値:6, ここまでの偶数の数: 4個
今回の値:7, ここまでの偶数の数: 4個
今回の値:8, ここまでの偶数の数: 5個
今回の値:9, ここまでの偶数の数: 5個
(2)複数の処理を実行
以下の例では、voidメソッド(init1、init2)の呼び出しと、init3 メソッドの戻り値を変数に代入する処理を初期化式として記述しています。
処理を複数個記述した場合は、左側から順に実行されます。
int i;
for(init1(), init2(), i = init3(); i < 10; i++) {
// 繰り返し実行したい処理
}
・条件式
関係演算子でつなぐことで、複数の条件を条件式に設定することが可能です。
演算子の比較の他、boolean(またはBoolean)が戻り値となるメソッドの結果を利用して判定することも可能です。
以下の例では、「i < 10 かつ cond1メソッドの戻り値がtrue」または「cond2メソッドの戻り値がtrue」の場合に、ループ処理が実行されます。
for(int i = 0; ( i < 10 && cond1() ) || cond2(); i++){
// 繰り返し実行したい処理
}
・変化式
変化式にも複数の処理を記述することが可能です。
複数の処理を記述する場合は、初期化式と同じようにカンマで区切って記述します。
以下の例では、カウンタ用の変数 i のインクリメントと、afterメソッドの呼び出しを、ループ1周ごとに毎回実行します。
複数個の処理を記述した場合、初期化式と同じように左側から順に実行されます。
for(int i = 0; i < 10; i++, after()) {
// 繰り返し実行したい処理
}
初期化式、条件式、変化式の省略
初期化式、条件式、変化式のいずれか、または全部を省略して記述することも可能です。
下記の例では、初期化式、条件式、変化式を全て省略して、その代替となる処理を異なる場所に記述しています。
// (1)初期化式の代替の処理
// forループのカウンタとして使う変数idxを、for文の外側で宣言
int idx = 0;
// 初期化式、条件式、変化式を全て省略
for( ; ; ) {
// (2)条件式の代替処理(for文のループの先頭で、ループの継続を判定)
if(idx >= 10) {
break; // for文のループを終了して次の処理へ
}
// 繰り返し実施したい処理
System.out.println(idx);
// (3)変化式の代替処理(for文のループの最後でカウンタ用の変数を+1)
idx++;
}
System.out.println("--forループを終了--");
// (おまけ)idx はfor文の外で宣言している変数なので、ループの後でも利用できる
System.out.println(idx); // 10 を出力
上記のコードでは、まず初期化式の代わりに、(1)の箇所でfor文の継続判定に利用する変数 idx を宣言しています。
続いて(2)の箇所で、ブロック内の後続処理を実行するかどうかを条件式の代わりに判定し、idx が10以上の場合はbreak句でループを終了して、次の処理へ移動しています。(break句の詳細については後述します)
また、変化式の代わりに、ブロックの最後の位置(3)でカウント用の変数 idx をインクリメントしています。
実行結果:
0
1
2
3
4
5
6
7
8
9
--forループを終了--
10
配列やListへの処理
配列やListの全要素に対して処理を行いたい場合は、対象の要素の数だけループすることで実現できます。要素の数は、配列の場合はlength、Listの場合はsizeメソッドで取得できます。
配列やListのインデックスは 0 から始まるので、ループカウンタ用の変数が「0 ~(要素数 - 1)」となるように条件式を指定し、またループカウンタの変数をインデックスとして要素を取得することで、配列またはListの全要素に対して繰り返し処理が行えます。
// 配列のループ処理 lengthを利用
String[] strArray = {"A", "B", "C"};
for(int i = 0; i < strArray.length; i++) {
System.out.println(strArray[i]); // i 番目の要素を取得して出力
}
// Listのループ処理 sizeを利用
List<String> strList = List.of("X", "Y", "Z");
for(int i = 0; i < strList.size(); i++) {
System.out.println(strList.get(i)); // i 番目の要素を取得して出力
}
実行結果:
A
B
C
X
Y
Z
全要素ではなく、最初のN個、などの条件でループ処理を行いたい場合は、同じようにループカウンタをインデックスとして利用しつつ、条件式で範囲を指定するなどしても構いません。
List<String> strList = List.of("X", "Y", "Z");
// Listの「最初の2個まで」ループ処理を行う
for(int i = 0; i < 2; i++) {
System.out.println(strList.get(i)); // i 番目の要素を取得して出力
}
実行結果:
X
Y
拡張for文
配列やListなどのコレクションの「全要素に対して」ループ処理を行いたい場合、ループカウンタ等を利用しない拡張for文という記述方法でも、全要素に対してループ処理を行うことができます。
拡張for文の記述形式は以下のとおりです。
for( 型 変数名 : ループ処理の対象 ) { 繰り返し実行したい処理 }
※ループ処理の対象は配列、コレクション等
拡張for文では、繰り返し処理を行う対象の配列、コレクションの各要素を変数に格納して、ループ処理の中でそのまま利用できます。(変数の型は、要素を格納可能な型である必要があります)
以下の例では、配列、Listの各要素を、拡張for文の中で宣言している「String str」に代入して、その変数名(str)でループ処理の中で利用しています。
String[] strArray = {"A", "B", "C"};
List<String> strList = List.of("X", "Y", "Z");
// 配列に対して拡張構文を利用
for(String str : strArray) {
System.out.println(str);
}
// Listに対して拡張for文を利用
for(String str : strList) {
System.out.println(str);
}
実行結果:
A
B
C
X
Y
Z
拡張for文は、配列と「Iterable」インターフェイスを実装しているクラスのみ利用が可能です。
コレクション・フレームワークでも、ArrayList や HashSet は拡張for文を利用できますが、HashMapでは拡張for文を利用できません。
また、HashSet などの順序を持たないコレクションでは、拡張for文で各要素の順序が保証されないことに注意してください。
ループ処理のネスト
多次元配列のループ
「配列の配列」のように、2次元以上のデータを持つオブジェクトのすべての要素に対して、同じ繰り返し処理を行いたい場合は、ループ処理の中にループ処理を記述することも可能です。
下記の例では、{"A-1", "A-2"}、{"B-1", "B-2", "B-3"}、{"C-1", "C-2"} の3つの配列を要素に持つ配列(2次元配列)のすべての要素に対して繰り返し処理を行っています。
// 2次元配列
String[][] str2dArray = {{"A-1", "A-2"}, {"B-1", "B-2", "B-3"}, {"C-1", "C-2"}};
// 親配列(1次元め)からループ
for(int i = 0; i < str2dArray.length; i++) {
// 子配列(i 番目の配列)を取得
String[] childArray = str2dArray[i];
for(int j = 0; j < childArray.length; j++) {
System.out.println(str2dArray[j]); // 子配列の j 番目の要素を出力
}
}
このように、ループ処理の中でさらにループ処理を行うことを、「ループ処理のネスト」と呼びます。
この配列の各要素が処理される順番は、下図のようになります。
実行結果:
A-1
A-2
B-1
B-2
B-3
C-1
C-2
break と continue
break
Javaのループ処理では、break キーワードを利用して任意のタイミングでループ処理を終了させ、次の処理へ移動することが可能です。
String[] strArray = {"A", "B", "C", "D", "E"};
// 「C」が見つかるまで処理を実行
for(String str : strArray) {
// 要素の文字列が「C」だった場合は、breakでループ処理を終了
if("C".equals(str)) {
System.out.println("Cがみつかったため、ループ処理を終了。");
break;
}
// 要素の文字列を出力
System.out.println(str);
}
break が実行された場合、即座に実行中のループ処理を終了し、ループ処理の次の処理へ移動します。
終了する際は、ループ処理の範囲の後続処理も実行されません。
実行結果:
A
B
Cがみつかったため、ループ処理を終了。
breakキーワードは、whileを用いたループ処理やswitch文といった構文でも、次の処理まで移動させるために利用することができます。
continue
Javaのループ処理では、continue キーワードを利用して、ループ処理内の任意のタイミングで次のループへ進むことができます。
List<Integer> intList = List.of(1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89);
for(int i = 0; i < intList.size(); i++) {
Integer number = intList.get(i);
// Listから取得した値が偶数だった場合は、以降のループ処理をスキップし次のループへ
if(number % 2 == 0) {
continue;
}
System.out.println( i + "番目の値:" + number);
}
ループ処理内で continue キーワードを呼び出した場合、その周のループ処理をその場で終了して、次の周のループ処理を行います。
この時、次のループの開始前に、変化式の処理は実行されます。
実行結果:
0番目の値:1
1番目の値:1
3番目の値:3
4番目の値:5
6番目の値:13
7番目の値:21
9番目の値:55
10番目の値:89