【Java入門】第17回 繰り返し処理(2)| while・do while 文の利用とイテレータ
2024.07.19
前回の記事(【Java入門】第16回 繰り返し処理(1))では、最もポピュラーな繰り返し処理としてfor文と拡張for文の利用方法を説明しましたが、Javaにはその他にも繰り返し処理を行う構文があります。
当記事では、for文以外の繰り返し処理の実行方法として、While文・do While文と、主にコレクションの繰り返し処理に利用できるイテレータ(Iterator)について説明します。
目次
while文とdo while文
while文
while文は繰り返し処理を行うための条件式だけを記述し、条件式を満たし続ける限り、繰り返し処理が実行されます。
実行条件は以下の形式で記述します。
while( 条件式 ) { 繰り返し実行したい処理 }
以下の例では、変数 sum の値が10に満たない限り、繰り返し sum の値を出力して、sumに3を加算しています。
int sum = 0;
// sumが10未満である限りループを行う
while(sum < 10) {
System.out.println("sumの値は " + sum + " です。");
sum += 3;
}
実行結果:
sumの値は 0 です。
sumの値は 3 です。
sumの値は 6 です。
sumの値は 9 です。
while文は、中の処理で条件式から外れるための処理が無い場合や、誤ったプログラムの実装をしてしまった場合など、永久に処理が実行(無限ループ)してしまうミスをしやすいため、注意してください。
以下の例では、変数 sum の値に加算する処理に誤った条件を付けてしまった場合(7行目)、無限にwhile文の処理を実行してしまいます。
※コピー&ペーストで実行されないように、7・9行目をコメントアウトしています。コメントを外して実行する場合は十分にご注意ください。
int sum = 0;
// sumが10未満である限りループを行う
while(sum < 10) {
System.out.println("sumの値は " + sum + " です。");
// if(sum == 0) { // この条件を付けてしまうと無限ループとなる(sumが10より大きくなることがないため)
sum += 3;
// }
}
※無限ループとなる処理を実行してしまった場合の対応
無限ループとなって処理が止まらなくなってしまった場合は、以下の方法で処理を中止することが可能です。
・コマンドプロンプト、コンソール等で実行している場合
→ キーボードで「Ctrl + c」で処理を終了することが可能です。
・Eclipse上で実行している場合
→ 「コンソール」ビューの「停止」ボタンを押して、処理を終了してください。
do while文
do while文は、while文と同様に条件式のみを記述して繰り返し処理を行う構文ですが、while文とは以下の差があります。
・ループ処理に記述された処理を、必ず最初に1回行う(条件式での判定は実行されない)
・2周目以降の処理を行うかどうかを、条件式で判定する
実行条件は以下の形式で記述します。
条件式の記述の後に、セミコロンが必要なことに注意してください。
do { 繰り返し実行したい処理 } while ( 条件式 );
以下の例では、変数sumの値を0、条件式を sum < 0 として、最初から条件式を満たさない状態でdo while文を実行しています。
実行結果から、最初の1周のみ処理が実行されていることがわかります。
int sum = 0;
// sum < 0 の場合はループを続行させる
do {
System.out.println("sumの値は " + sum + " です。");
sum += 1;
} while ( sum < 0 );
実行結果:
(条件式が満たされないため、初回の do での処理だけが実行される)
sumの値は 0 です。
do while文の場合は、1回目の実行時に条件を満たしていてもいなくても、同じ動作となる場合があることに注意してください。(条件式の判定が実行されるのは「2周目以降」のループが対象)
以下のコードでは、先ほどのコードから条件式だけを変え(「sum < 0」を「sum <= 0」に変更 )、1回目の実行時に条件を満たすようにしてありますが、1回目から条件を満たさなかった上記の例と同じ動作結果になります。
int sum = 0;
// sum < 0 の場合はループを続行させる
do {
System.out.println("sumの値は " + sum + " です。");
sum += 1;
} while ( sum <= 0 );
実行結果:
sumの値は 0 です。
while文、do whlie文の記述と、各々の判定式の実行タイミングは、以下のようになります。
イテレータ(Iterator・反復子)の利用
イテレータとは
イテレータ(イテレーター)はコレクション・フレームワークなどの「データの集合」の各要素に対し、順番にアクセスするためのオブジェクトです。イテレータは「Iterableインターフェイス」を実装しているクラスで利用することができます。
Javaのイテレータはジェネリクスに対応しており、コレクション・フレームワークのクラスでは自身の格納するデータの型に応じたイテレータが提供されます。
(List<String>であれば、Iterator<String>が提供される)
イテレータは「データの集合」そのものを操作せず、格納された各要素へのアクセスを提供する仕組みとなっており、以下のメソッドを用いることで、特にwhile文でのループ処理に適しています。
メソッド名 | 型 | 説明 |
hasNext | boolean | 次の要素を持っている場合true、持っていない場合はfalseを返却する。 → While文の条件式に利用することで、要素がある限り実行するループ処理を記述できる。 |
next | T | 次の要素を取得する。 |
前回説明した拡張for文は、この「Iterableインターフェイスを実装した(Itaratorを返す)クラス」か、配列のいずれかの場合だけ利用することができます。
イテレータを利用したコレクションへの繰り返し処理
・Set
Setの各要素にアクセスするためのイテレータを「iterator」メソッドで取得することが可能です。
以下の例ではHashSetの各要素を、イテレータ経由で取得して出力しています。
HashSetは順序を持たないコレクションであり、そのイテレータも同様であることに注意してください。
var strSet = new HashSet<String>();
strSet.add("string1");
strSet.add("string2");
strSet.add("string3");
strSet.add("string4");
strSet.add("string5");
// Setのイテレータを取得
Iterator<String> setItr = strSet.iterator();
// すべての項目を出力
while(setItr.hasNext()) {
String str = setItr.next();
System.out.println(str);
}
実行結果:
string5
string3
string4
string1
string2
・List
Listの各要素にアクセスするためのイテレータを「iterator」メソッドで取得することが可能です。
以下の例ではListの各要素を、イテレータ経由で取得して出力しています。
Listは順序を持ち、イテレータからは項目が追加された順にアクセスされます。
var strList = List.of("A", "B", "C", "D", "E");
// Listのイテレータを取得
Iterator<String> strItr = strList3.iterator();
// すべての項目を出力
while(strItr.hasNext()) {
String str = strItr.next();
System.out.println(str);
}
実行結果:
A
B
C
D
E
・Map
Mapはキーと値の組み合わせを持ちますが、この組み合わせへのイテレータは準備されていません。
かわりに、Mapでは「keySet」メソッドでキーのSetにアクセスできますので、このSetのイテレータを利用することで、繰り返し処理を実行することができます。
下記の例ではキー、値ともStringのHashMapに対して、keySetのイテレータを取得して、ループ処理で各キーで値を取得して処理を行っています。
HashMapも順序を持たないコレクションなので、そのkeySetのイテレータも同様となります。
var strMap = new HashMap<String, String>();
strMap.put("key1", "A");
strMap.put("key2", "B");
strMap.put("key3", "C");
strMap.put("key4", "D");
strMap.put("key5", "E");
// Mapの「キー」のイテレータを取得
Iterator<String> keyItr = strMap.keySet().iterator();
// 各々のキーで値を取得して全て出力
while(keyItr.hasNext()) {
String value = strMap.get(keyItr.next());
System.out.println(value);
}
実行結果:
key=key1, value=A
key=key2, value=B
key=key5, value=E
key=key3, value=C
key=key4, value=D
多重ループ
複数形式を利用した多重ループ
前回、多次元配列に対してループ処理のネストについて説明しましたが、『コレクションのコレクション(ListのList、など)』のような多次元のデータに対する処理を行う場合などは、複数の構文を重ねてループ処理のネストを記述することも可能です。
下記のサンプルでは、「List を格納した Map」を準備し、Mapに格納されている全てのListに対する処理を行っています。また、Listに対しては、格納されている全てのStringを出力しています。
var studentClassMap = new HashMap<String, List<String>>();
studentClassMap.put("生徒A", List.of("1年1組", "2年1組", "3年1組"));
studentClassMap.put("生徒B", List.of("1年2組", "2年2組", "3年2組"));
studentClassMap.put("生徒C", List.of("1年3組", "2年3組", "3年3組"));
Iterator<String> nameItr = studentClassMap.keySet().iterator();
// Mapのキー項目のイテレータに対してループさせる
while(nameItr.hasNext()) {
// マップのkey(生徒)を、イテレータから取得し、対応するListを取得
String name = nameItr.next();
var list = studentClassMap.get(name);
// マップのkey(生徒)を出力
System.out.println("氏名:" + name);
// 続けて生徒の所属クラスをListから出力
for(String homeClass : list) {
System.out.println(homeClass);
}
}
実行結果:
氏名:生徒C
1年3組
2年3組
3年3組
氏名:生徒B
1年2組
2年2組
3年2組
氏名:生徒A
1年1組
2年1組
3年1組
break とラベル
前回はループ処理を途中で終了して、ループ処理の次の処理へ移動するために、break句を利用しました。
では、ループのネストの中でbreakを実行した場合の動作はどのようになるでしょうか。
ループのネスト内のbreak
ネストしているループ処理の中でbreakを実行した場合は、通常は「現在実行中のループ」を終了させます。
下記の例では「ListのMap」に対してネストしたループ処理(Map -> List の 順)を行い、Listのループ処理の中でbreakを実行しています。
この場合、現在実行中のListに対するループ処理は中断されますが、外側のループであるMapに対するループ処理は継続されていることが分かります。
var studentClassMap = new HashMap<String, List<String>>();
studentClassMap.put("生徒A", List.of("1年1組", "2年1組", "3年1組"));
studentClassMap.put("生徒B", List.of("1年2組", "2年2組", "3年2組"));
studentClassMap.put("生徒C", List.of("1年3組", "2年3組", "3年3組"));
Iterator<String> nameItr = studentClassMap.keySet().iterator();
// Mapのキー項目のイテレータに対してループさせる
while(nameItr.hasNext()) {
// マップのkey(生徒)を、イテレータから取得し、対応するListを取得
String name = nameItr.next();
var list = studentClassMap.get(name);
// マップのkey(生徒)を出力
System.out.println("氏名:" + name);
// 続けて生徒の所属クラスをListから出力
for(String homeClass : list) {
System.out.println(homeClass);
// 2で始まる文字列だったらbreak
if(homeClass.startsWith("2")) {
break;
}
}
}
実行結果:
氏名:生徒C
1年3組
2年3組
氏名:生徒B
1年2組
2年2組
氏名:生徒A
1年1組
2年1組
ラベル付きbreak文(階層を指定したbreak)
Javaではラベル付きbreak文という、「どのループを終了させるか」を選択してbreakする仕組みも準備されています。
ラベル付きbreak文を利用するためには、breakの対象としたいループ処理にラベルを設定しておく必要があります。
ラベルのつけ方は、for文またはwhile文の直前に XXX : (ループ名+コロン)と記述します。
以下の例では、外側のループ処理に「outerloop」、内側のループに「innerloop」というラベルを付け、内側のループから外側のループ処理ごと終了させています。
var studentClassMap = new HashMap<String, List<String>>();
studentClassMap.put("生徒A", List.of("1年1組", "2年1組", "3年1組"));
studentClassMap.put("生徒B", List.of("1年2組", "2年2組", "3年2組"));
studentClassMap.put("生徒C", List.of("1年3組", "2年3組", "3年3組"));
Iterator<String> nameItr = studentClassMap.keySet().iterator();
outerloop: // 外側のループにラベル「outerloop」を設定
while(nameItr.hasNext()) {
// マップのkey(生徒)を、イテレータから取得し、対応するListを取得
String name = nameItr.next();
var list = studentClassMap.get(name);
// 続けて生徒の所属クラスをListから出力
System.out.println("氏名:" + name);
innerloop: // 外側のループにラベル「innerloop」を設定
for(String homeClass : list) {
System.out.println(homeClass);
// 2で始まる文字列だったら「outerloop」を指定してbreak
if(homeClass.startsWith("2")) {
break outerloop; // outerloop のループ処理をbreak
}
}
}
実行結果:
(Mapのループを終了させたため、1個のListに対してのみ処理が実行されています)
氏名:生徒C
1年3組
2年3組