- JAVA
Java while文とdo-whileの使い方|breakと無限ループ
Javaのwhile文とは、条件式がtrueの間だけ同じ処理をくり返す構文です。do-while文は「先に1回実行してから条件を確かめる」形で、本体が必ず1回は動く点がwhile文と異なります。 くり返す回数が先に決まっていないとき(入力が尽きるまで、目的の値が見つかるまで、成功するまで)に向いています。
この記事では、while文・do-while文の基本から、break・continue、ラベル付きbreak、無限ループの原因と直し方までを順に説明します。すべてのサンプルはOpenJDK 21.0.10で java ファイル名.java として動かし、コンソール出力をそのまま載せています。 無限ループやコンパイルエラーも実際に再現したので、手元で同じ表示が出たときの照らし合わせに使えます。
while文・do-while文・for文の早見表
| 構文 | 条件を確かめるタイミング | 本体の最小実行回数 | 向いている場面 | 書き方の骨組み |
|---|---|---|---|---|
| while | 本体の前 | 0回 | 回数が決まっていないくり返し(入力が尽きるまで、見つかるまで) | while (条件) { 処理 } |
| do-while | 本体の後 | 1回 | まず1回実行してから続けるか決める(入力のやり直し、6が出るまで振る) | do { 処理 } while (条件); |
| for | 本体の前 | 0回 | 回数や範囲が決まっているくり返し(0からn-1まで) | for (初期化; 条件; 更新) { 処理 } |
| 拡張for | 要素がある間 | 0回 | 配列やListの全要素を順に処理 | for (型 x : 集まり) { 処理 } |
ループの流れを変える文は次の4つです。
| 文 | 動き | 主な使いどころ |
|---|---|---|
break; |
いちばん内側のループを抜ける | 目的の値が見つかった、終了の合図を受け取った |
continue; |
残りの処理を飛ばして次の周へ進む | 空行や対象外のデータを読み飛ばす |
break ラベル; |
ラベルを付けた外側のループごと抜ける | 二重ループの内側から全体を終える |
continue ラベル; |
ラベルを付けた外側のループの次の周へ進む | 内側で条件に当たったら外側の次の要素へ |
つまずきやすい点と表示されるメッセージも先に並べておきます。
| 症状 | 主な原因 | 直し方 |
|---|---|---|
| 同じ出力が止まらず続く | カウンタの更新を書いていない | 本体に i++ などの更新を入れる |
| 何も表示されず止まらない | while (条件); と条件の後ろにセミコロン |
セミコロンを取り除く |
error: unreachable statement |
while (true) の後ろに到達できない文がある、breakの直後に文がある |
出口のbreakを入れる、文の順番を見直す |
error: cannot find symbol(do-while) |
本体の中で宣言した変数を条件式で使っている | 変数をdoの前で宣言する |
error: undefined label |
ラベル名の書き間違い、ラベルの外でbreak | ラベルをループの直前に付ける |
ArrayIndexOutOfBoundsException |
条件が <= で1回多い |
< に直す |
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「Java while文とdo-whileの使い方|breakと無限ループ」
while文の基本の書き方
while文は「初期化 → 条件式 → 本体 → 更新」の4つを自分で並べて書きます。 条件式がtrueなら本体を実行し、本体の終わりまで来たら条件式に戻ります。falseになった時点でループを終えます。
public class WhileBasic {
public static void main(String[] args) {
int i = 1; // 1. 初期化
while (i <= 3) { // 2. 条件式(trueの間くり返す)
System.out.println("i = " + i);
i++; // 3. 更新
}
System.out.println("ループ後の i = " + i);
}
}
実行コマンドと結果です(以降のサンプルも同じ形で動かしています)。
$ java WhileBasic.java
i = 1
i = 2
i = 3
ループ後の i = 4
処理の流れを図にすると次のようになります。
flowchart TD
A[初期化 i = 1] --> B{条件式 i <= 3}
B -- true --> C[本体を実行]
C --> D[更新 i++]
D --> B
B -- false --> E[ループの次の文へ]
押さえておきたい点は3つです。
- 条件式はboolean型(trueかfalse)になる式だけが書けます。
while (1)のような数値は書けません。 - ループを終えた時点で、iは「条件を満たさなくなった最初の値」になっています。 上の例では4です。
- 本体が1文でも波かっこ
{ }で囲むと、後から処理を足したときに範囲がずれません。
条件を複数組み合わせる
条件は &&(かつ)と ||(または)でつなげられます。次の例は「成功するか、上限回数に達するまで」くり返します。
public class Retry {
static int calls = 0;
// 3回目で成功する処理の代わり
static boolean tryConnect() {
calls++;
return calls >= 3;
}
public static void main(String[] args) {
final int MAX = 5;
int attempt = 0;
boolean ok = false;
while (!ok && attempt < MAX) { // 成功するか、上限に達するまで
attempt++;
ok = tryConnect();
System.out.println(attempt + "回目: " + (ok ? "成功" : "もう一度"));
}
System.out.println(ok ? "完了" : "上限 " + MAX + " 回に達しました");
}
}
1回目: もう一度
2回目: もう一度
3回目: 成功
完了
「続ける条件」を書くのがwhile文です。 「成功したら終わる」は「成功していない間は続ける(!ok)」と読み替えます。終わる条件が2つあるときは、それぞれを「続ける条件」に直して && でつなぐと整理しやすくなります。
| 終わる条件(日本語) | 続ける条件(while に書く式) |
|---|---|
| 成功した、または5回試した | !ok && attempt < 5 |
| 在庫が0になった | stock > 0 |
| 入力が尽きた | sc.hasNextLine() |
| 読み込んだ行がnullになった | (line = reader.readLine()) != null |
do-while文の基本の書き方
do-while文は本体を先に実行し、その後で条件式を確かめます。 while (条件) の後ろにセミコロンが必要な点が、while文との書き方の違いです。
public class DoWhileBasic {
public static void main(String[] args) {
int i = 1;
do {
System.out.println("i = " + i);
i++;
} while (i <= 3); // 末尾のセミコロンが必要
System.out.println("ループ後の i = " + i);
}
}
i = 1
i = 2
i = 3
ループ後の i = 4
条件が最初から成り立つ場合、出力はwhile文と同じになります。違いが出るのは、最初から条件を満たさないときです。
whileとdo-whileの違いを出力で確かめる
同じ値(0)から始めて、両方のループを動かします。
public class CompareFirst {
public static void main(String[] args) {
int stock = 0; // 最初から条件を満たさない値
System.out.println("--- while ---");
int a = stock;
while (a > 0) {
System.out.println("while: 残り " + a);
a--;
}
System.out.println("while の本体実行回数: 0回");
System.out.println("--- do-while ---");
int b = stock;
int count = 0;
do {
System.out.println("do-while: 残り " + b);
b--;
count++;
} while (b > 0);
System.out.println("do-while の本体実行回数: " + count + "回");
}
}
--- while ---
while の本体実行回数: 0回
--- do-while ---
do-while: 残り 0
do-while の本体実行回数: 1回
while文は本体を1回も実行せず、do-while文は「残り 0」を1回表示しました。 「0件のときは何もしない」が正しい動きならwhile文、「まず1回やってみる」が正しい動きならdo-while文を選びます。
do-whileが合う例1:条件を満たすまで入力をやり直す
入力値は「まず読んでみないと判定できない」ので、do-while文と相性のよい題材です。
import java.util.Scanner;
public class DoWhileInput {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n; // 条件式でも使うので外で宣言
do {
System.out.print("1〜5の数字を入力: ");
n = Integer.parseInt(sc.nextLine().trim());
System.out.println("入力値 = " + n);
} while (n < 1 || n > 5); // 範囲外ならもう一度
System.out.println("採用: " + n);
}
}
入力をパイプで渡して動かした結果です(9、0、3 の順に入力)。
$ printf '9\n0\n3\n' | java DoWhileInput.java
1〜5の数字を入力: 入力値 = 9
1〜5の数字を入力: 入力値 = 0
1〜5の数字を入力: 入力値 = 3
採用: 3
同じ処理をwhile文で書くと、ループの前にも「入力を読む」処理を1回書くことになります。同じ処理をループの前と中に2回書きたくなったら、do-while文に置き換えられる合図です。
do-whileが合う例2:目的の値が出るまでくり返す
public class DoWhileMenu {
public static void main(String[] args) {
int[] dice = {2, 5, 6, 1}; // サイコロの出目の代わり
int idx = 0;
int roll;
int times = 0;
do {
roll = dice[idx++];
times++;
System.out.println(times + "投目: " + roll);
} while (roll != 6); // 6が出るまで振る
System.out.println(times + "回で6が出ました");
}
}
1投目: 2
2投目: 5
3投目: 6
3回で6が出ました
出力を固定するために出目を配列で用意しましたが、java.util.Random の nextInt(6) + 1 に置き換えれば本物のサイコロになります。
do-whileでつまずく点:本体で宣言した変数は条件式で使えない
do-while文の条件式は波かっこの外にあります。そのため、本体の中で宣言した変数は条件式から見えません。
public class DoScope {
public static void main(String[] args) {
do {
int n = 3;
System.out.println(n);
} while (n < 0);
}
}
DoScope.java:6: error: cannot find symbol
} while (n < 0);
^
symbol: variable n
location: class DoScope
1 error
error: compilation failed
直し方は、先ほどの DoWhileInput のように 変数の宣言を do の前に出すことです(int n; を外に置き、本体では代入だけ行う)。
while・do-while・forの使い分け
3つの構文はどれも同じくり返しを書けます。選ぶ基準は「回数が先に決まっているか」「最低1回は実行したいか」の2点です。
| 確かめること | はい | いいえ |
|---|---|---|
| くり返す回数や範囲が、ループに入る前に決まっている | for文(配列・Listの全要素なら拡張for文) | 次の行へ |
| 条件を確かめる前に、まず1回は本体を実行したい | do-while文 | while文 |
題材ごとの目安は次のとおりです。
| やりたいこと | 選ぶ構文 | 理由 |
|---|---|---|
| 0からn-1まで順に処理する | for | 初期化・条件・更新が1行にまとまり、更新漏れが起きにくい |
| 配列・Listの全要素を表示する | 拡張for | 添字を自分で管理しなくてよい |
| 入力やファイルの行が尽きるまで読む | while | 回数が事前にわからない |
| 数値の桁を1桁ずつ取り出す | while | 0になるまで割り続ける形になる |
| 成功するまで、上限回数つきで試す | while | 条件が2つあり、回数も結果も決まっていない |
| Iteratorで要素を取り出しながら削除する | while | hasNext() がtrueの間という条件になる |
| 正しい値が入力されるまで聞き直す | do-while | 1回は入力を受け取る必要がある |
| メニューを表示して「終了」が選ばれるまで続ける | do-while | 1回は表示する必要がある |
同じ処理をforとwhileで書き比べる
public class ForVsWhile {
public static void main(String[] args) {
// for文:初期化・条件・更新が1行にまとまる
for (int i = 0; i < 3; i++) {
System.out.println("for i = " + i);
}
// 同じ処理を while文で書く
int j = 0;
while (j < 3) {
System.out.println("while j = " + j);
j++;
}
// for の i はここでは使えない。while の j は使える
System.out.println("ループ後の j = " + j);
}
}
for i = 0
for i = 1
for i = 2
while j = 0
while j = 1
while j = 2
ループ後の j = 3
違いは変数の有効範囲(スコープ)にも表れます。
| 観点 | for | while |
|---|---|---|
| カウンタ変数の宣言位置 | forの丸かっこの中 | ループの前 |
| ループ後にカウンタを使えるか | 使えない(forの中だけ有効) | 使える |
| 更新を書く位置 | 丸かっこの中に固定 | 本体の中の好きな位置 |
| 更新漏れの起きやすさ | 起きにくい | 自分で書くため起きやすい |
「ループが終わった後に、どこまで進んだかを使いたい」ときはwhile文が自然です。 逆に、カウンタをループの外へ出したくないときはfor文が合います。
breakとcontinueの使い方
breakは「ループをそこで終える」、continueは「この周の残りを飛ばして次の周へ進む」文です。 while文・do-while文・for文のどれでも同じように使えます。
public class BreakContinue {
public static void main(String[] args) {
int i = 0;
while (i < 10) {
i++; // continue より前で更新する
if (i % 2 == 0) {
continue; // 偶数は飛ばして次の周へ
}
if (i > 7) {
break; // 7を超えたらループを抜ける
}
System.out.println("i = " + i);
}
System.out.println("終了時の i = " + i);
}
}
i = 1
i = 3
i = 5
i = 7
終了時の i = 9
iが2、4、6、8のときはcontinueで表示を飛ばし、9になった時点でbreakによりループを抜けています。
| 文 | while文での飛び先 | do-while文での飛び先 | for文での飛び先 |
|---|---|---|---|
break |
ループの次の文 | ループの次の文 | ループの次の文 |
continue |
条件式 | 条件式(末尾のwhile) | 更新式(その後に条件式) |
for文のcontinueは更新式を通りますが、while文のcontinueは条件式へ直接戻ります。 この違いが、次に紹介する「continueで更新を飛ばしてしまう」動きにつながります。
continueの位置と更新の順番
更新(i++)より前にcontinueを書くと、更新が実行されないまま条件式へ戻ります。確認のため、8周で打ち切る仕組みを入れて動かしました。
public class ContinueTrap {
public static void main(String[] args) {
int i = 0;
int guard = 0; // 確認用:周回数の上限
while (i < 5) {
if (++guard > 8) {
System.out.println("8周で打ち切り(i = " + i + " のまま)");
break;
}
if (i == 2) {
continue; // 更新の前に continue → i が進まない
}
System.out.println("i = " + i);
i++;
}
}
}
i = 0
i = 1
8周で打ち切り(i = 2 のまま)
iが2になったところで先へ進まなくなりました。打ち切りの仕組みがなければ止まらないループになります。while文でcontinueを使うときは、更新をcontinueより前に書く(先ほどの BreakContinue の形)か、for文に置き換えると安心です。
入力が尽きるか「end」が来るまで読む
breakとcontinueを組み合わせた、よく使う形です。空行は読み飛ばし、end が来たら終わります。
import java.util.Scanner;
public class InputLoop {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int sum = 0;
int count = 0;
while (sc.hasNextLine()) { // 入力がある間くり返す
String line = sc.nextLine().trim();
if (line.equals("end")) {
break; // 終了の合図
}
if (line.isEmpty()) {
continue; // 空行は飛ばす
}
sum += Integer.parseInt(line);
count++;
}
System.out.println("件数 = " + count + ", 合計 = " + sum);
}
}
$ printf '10\n\n25\n7\nend\n99\n' | java InputLoop.java
件数 = 3, 合計 = 42
2行目の空行は数えず、end より後ろの99は読まれていません。
while (true) で書く無限ループとbreak
条件式に true を直接書くと、条件ではループが終わらなくなります。出口は本体の中のbreak(またはreturn)で作ります。 「終わる条件を本体の途中で判定したい」ときに使う、意図的な無限ループです。
public class InfiniteBreak {
public static void main(String[] args) {
int total = 0;
int n = 0;
while (true) { // 条件を常に true にする
n++;
total += n;
if (total >= 20) {
break; // 出口は本体の中に置く
}
}
System.out.println("n = " + n + ", total = " + total);
}
}
n = 6, total = 21
1から順に足していき、合計が20以上になった時点(1+2+3+4+5+6=21)で抜けています。
| 書き方 | 意味 | 補足 |
|---|---|---|
while (true) { ... } |
無限ループ | もっともよく見る形 |
for (;;) { ... } |
無限ループ | forの3つの式をすべて省略した形 |
do { ... } while (true); |
無限ループ | 本体を先に実行する形 |
while (true) を使うときの目安は「出口が本体の中に1〜2か所あり、ひと目で見つけられること」です。 出口が増えてきたら、条件式に戻すか、メソッドに切り出してreturnで抜ける形にすると読みやすくなります。
意図しない無限ループの原因と直し方
ここからは、止まらなくなる例を実際に動かして確かめます。再現には timeout コマンドを使い、指定した秒数で打ち切りました。
原因1:カウンタの更新を書き忘れる
public class ForgetUpdate {
public static void main(String[] args) {
int i = 1;
while (i <= 3) {
System.out.println("i = " + i);
// i++; を書き忘れた
}
System.out.println("終了");
}
}
2秒で打ち切り、出力をファイルに保存して先頭3行と行数を確かめました。
$ timeout 2 java ForgetUpdate.java > fu.txt 2>&1; echo "[exit $?]"
[exit 124]
$ head -3 fu.txt
i = 1
i = 1
i = 1
$ wc -l < fu.txt
1451259
2秒間で約145万行の「i = 1」が出力されました(行数は環境や回ごとに変わります)。 終了コード124は、timeoutが時間切れでプログラムを止めたことを表します。iが1のまま変わらないので、条件 i <= 3 はずっとtrueです。
直した形はこちらです。
while (i <= 3) {
System.out.println("i = " + i);
i++; // 更新を追加
}
i = 1
i = 2
i = 3
終了
原因2:条件の後ろにセミコロンを付ける
public class Semicolon {
public static void main(String[] args) {
int i = 1;
while (i <= 3); // ← 条件の後ろにセミコロン
{
System.out.println("i = " + i);
i++;
}
System.out.println("終了");
}
}
$ timeout 3 java Semicolon.java; echo "[exit $?]"
[exit 124]
今度は何も表示されないまま止まらず、3秒で打ち切られました。 while (i <= 3); のセミコロンは「何もしない文(空文)」として扱われ、これがループの本体になります。その下の波かっこは、ループとは関係のないただのブロックです。iが変わらないので、空文を永遠にくり返します。
| 書いたコード | Javaの解釈 |
|---|---|
while (i <= 3); |
本体が空文のwhile文。ここで止まらなくなる |
{ System.out.println(...); i++; } |
ループとは別の、ただのブロック(到達しない) |
この書き方はコンパイルエラーになりません。javac -Xlint:all Semicolon.java でも警告は表示されませんでした(JDK 21.0.10で確認)。「何も表示されずに止まらない」ときは、whileの行末のセミコロンを最初に疑うと早く見つかります。 なお、do-while文の末尾のセミコロン(} while (条件);)は必要なものなので、混同しないようにします。
原因3:更新の向きが逆になっている
public class WrongDirection {
public static void main(String[] args) {
int i = 1;
long count = 0;
while (i > 0) { // 減らすつもりが増やしている
i++;
count++;
}
System.out.println("周回数 = " + count);
System.out.println("終了時の i = " + i);
}
}
周回数 = 2147483647
終了時の i = -2147483648
このループは止まりましたが、約21億周しています。int型の最大値2147483647に1を足すと、最小値-2147483648に回り込む(オーバーフロー)ため、そこで i > 0 がfalseになりました。「止まったが時間がかかる」「終了時の値が大きな負の数」という症状は、更新の向きを確かめる合図です。
原因4:小数を != で比べている
public class DoubleLoop {
public static void main(String[] args) {
double x = 0.0;
int guard = 0;
while (x != 1.0) { // 小数を != で比べる
x += 0.1;
if (++guard >= 12) {
System.out.println("12周で打ち切り x = " + x);
break;
}
}
// 直した形:整数で回数を数える
int k = 0;
while (k < 10) {
k++;
}
System.out.println("整数カウンタ k = " + k + " → " + (k / 10.0));
}
}
12周で打ち切り x = 1.2
整数カウンタ k = 10 → 1.0
0.1を10回足しても、double型ではちょうど1.0になりません。そのため x != 1.0 はtrueのまま通り過ぎ、打ち切りの仕組みがなければ止まりません。回数は整数で数え、小数は計算で求める形にすると、ねらった回数で終わります。
止まらなくなったときの対処
| 状況 | 操作 |
|---|---|
| ターミナル・コマンドプロンプトで実行中 | Ctrl + C で停止する |
| IDE(Eclipse、IntelliJ IDEA など)で実行中 | コンソールの停止ボタン(赤い四角)を押す |
| 原因を調べたい | ループの先頭でカウンタと条件に使う変数を表示する |
| 再発を防ぎたい | 確認中は上限回数(guard)を入れて打ち切る |
無限ループの原因を見つけるチェックリストです。
| 確かめる点 | 見るところ |
|---|---|
| 条件に使う変数が本体の中で変わっているか | i++ などの更新があるか |
| 更新がcontinueより前にあるか | continueで更新が飛ばされていないか |
| whileの行末にセミコロンがないか | while (...); になっていないか |
| 更新の向きが条件と合っているか | i > 0 なら減らす、i < n なら増やす |
小数を == や != で比べていないか |
整数のカウンタに置き換える |
| 内側ループで外側のカウンタを書き換えていないか | 二重ループの変数名を確かめる |
unreachable statement(到達できない文)のコンパイルエラー
Javaのコンパイラは「絶対に実行されない文」を見つけるとエラーにします。 while文まわりでは、次の3つの形でよく出会います。
形1:while (true) の後ろに文がある
public class Unreachable1 {
public static void main(String[] args) {
int i = 0;
while (true) {
i++;
}
System.out.println("終了 " + i);
}
}
Unreachable1.java:7: error: unreachable statement
System.out.println("終了 " + i);
^
1 error
error: compilation failed
条件が定数の true で、本体にbreakがないため、ループの後ろには決して到達しません。出口のbreakを入れると、後ろの文に到達できるようになりエラーが消えます。
while (true) {
i++;
if (i >= 5) {
break; // 出口ができたので後ろの文に到達できる
}
}
System.out.println("終了 " + i);
終了 5
形2:breakやcontinueの直後に文がある
while (i < 3) {
i++;
break;
System.out.println("i = " + i);
}
Unreachable2.java:7: error: unreachable statement
System.out.println("i = " + i);
^
1 error
error: compilation failed
breakの次の行には進めないため、エラーになります。breakやcontinueはif文の中に入れるか、ブロックの最後に置きます。
形3:while (false) と書く
public class Unreachable3 {
public static void main(String[] args) {
while (false) {
System.out.println("ここは実行されない");
}
}
}
Unreachable3.java:3: error: unreachable statement
while (false) {
^
1 error
error: compilation failed
条件が定数の false だと、本体が一度も実行されないことがコンパイル時にわかるためエラーになります。
| 書き方 | コンパイル結果 | 理由 |
|---|---|---|
while (true) { } の後ろに文(breakなし) |
unreachable statement | ループを抜ける道がない |
while (true) { if (...) break; } の後ろに文 |
通る | breakで抜けられる |
break; や continue; の直後に文 |
unreachable statement | 直後の文へ進めない |
while (false) { ... } |
unreachable statement | 本体に入れない |
boolean on = true; while (on) { } の後ろに文 |
通る | 変数は定数として扱われないため |
最後の行のように、条件が変数なら、値が変わらなくてもコンパイルは通ります。 その場合はコンパイルエラーではなく、動かしたときの無限ループとして現れます。
breakに関するそのほかのコンパイルエラー
ループやswitchの外でbreakを書いた場合と、存在しないラベルを指定した場合のメッセージです。
BreakOutside.java:5: error: break outside switch or loop
break;
^
LabelErr.java:6: error: undefined label: outer
break outer;
^
| メッセージ | 意味 | 直し方 |
|---|---|---|
break outside switch or loop |
ループでもswitchでもない場所(if文だけの中など)でbreakを書いた | breakをループの中へ移す、またはreturnを使う |
undefined label: 名前 |
その名前のラベルが、breakを囲む位置に付いていない | ラベルを外側ループの直前に付ける、名前をそろえる |
ラベル付きbreak・continueとフラグ変数の書き比べ
二重ループの内側で普通のbreakを書くと、抜けるのは内側のループだけです。 まずこの動きを確かめます。
public class LabelBreakPlain {
public static void main(String[] args) {
int r = 0;
while (r < 3) {
int c = 0;
while (c < 3) {
if (r == 1 && c == 1) {
break; // ラベルなし:内側だけ抜ける
}
System.out.println("r=" + r + " c=" + c);
c++;
}
r++;
}
}
}
r=0 c=0
r=0 c=1
r=0 c=2
r=1 c=0
r=2 c=0
r=2 c=1
r=2 c=2
r=1 c=1 で内側を抜けた後も、外側のループは続いて r=2 の行が表示されています。外側ごと終えたいときの書き方は2通りあります。
外側ごと抜ける:フラグ変数とラベル付きbreak
二次元配列から42を探し、見つけたら全体を終える処理を、2つの方法で書きました。
public class LabelBreak {
public static void main(String[] args) {
int[][] table = {
{3, 8, 5},
{7, 42, 1},
{9, 4, 6}
};
int target = 42;
// ---- A. フラグ変数で抜ける ----
boolean found = false;
int row = 0;
while (row < table.length && !found) {
int col = 0;
while (col < table[row].length) {
if (table[row][col] == target) {
System.out.println("A: 見つけた [" + row + "][" + col + "]");
found = true;
break; // 内側だけ抜ける
}
col++;
}
row++;
}
// ---- B. ラベル付き break で抜ける ----
int r = 0;
search:
while (r < table.length) {
int c = 0;
while (c < table[r].length) {
if (table[r][c] == target) {
System.out.println("B: 見つけた [" + r + "][" + c + "]");
break search; // 外側ごと抜ける
}
c++;
}
r++;
}
System.out.println("B: 終了時の r = " + r);
}
}
A: 見つけた [1][1]
B: 見つけた [1][1]
B: 終了時の r = 1
ラベルは 名前: の形でループの直前に書き、break 名前; でそのループごと抜けます。 Bでは r++ を通らずに抜けるため、終了時のrは見つけた行(1)のままです。Aのフラグ方式では、breakの後に row++ を通ってから外側の条件で終わるため、ループ後のrowは見つけた行の次(2)になります。見つけた位置を後で使うなら、位置を別の変数に控えておくと確実です。
外側の次の周へ進む:フラグ変数とラベル付きcontinue
各行をカンマで区切り、「NG」を含む行を飛ばして表示する処理です。
public class LabelContinue {
public static void main(String[] args) {
String[] lines = {"apple,banana", "cherry,NG,grape", "melon,peach"};
// ---- A. フラグ変数 ----
int i = 0;
while (i < lines.length) {
String[] items = lines[i].split(",");
boolean skip = false;
int j = 0;
while (j < items.length) {
if (items[j].equals("NG")) {
skip = true;
break;
}
j++;
}
if (!skip) {
System.out.println("A: OK " + lines[i]);
}
i++;
}
// ---- B. ラベル付き continue ----
int p = -1;
outer:
while (++p < lines.length) { // 更新を条件式に入れておく
String[] items = lines[p].split(",");
int q = 0;
while (q < items.length) {
if (items[q].equals("NG")) {
continue outer; // 外側ループの次の周へ
}
q++;
}
System.out.println("B: OK " + lines[p]);
}
}
}
A: OK apple,banana
A: OK melon,peach
B: OK apple,banana
B: OK melon,peach
Bでは continue outer; が外側の条件式へ戻るので、外側の更新を条件式に入れています(++p < lines.length)。 本体の末尾に p++ を置く形だと、continueで更新が飛ばされて止まらなくなるためです。外側をfor文にすれば更新式を必ず通るので、ラベル付きcontinueはfor文と組み合わせるほうが書きやすくなります。
どちらを選ぶかの目安
| 観点 | フラグ変数 | ラベル付きbreak/continue |
|---|---|---|
| 行数 | 多め(変数の宣言・代入・判定が要る) | 少なめ |
| 抜ける位置の読み取りやすさ | 条件式と本体の両方を読む必要がある | break 名前; の1行でわかる |
| ループ後の後処理 | フラグの値で分岐しやすい | 別途、結果を変数に控える |
| 三重以上のループ | フラグの判定が各段に必要 | ラベル1つで外まで抜けられる |
| なじみやすさ | 多くの言語に共通する書き方 | Java特有の書き方で、初めて見る人には説明が要る |
もう1つの選択肢は、二重ループをメソッドに切り出してreturnで抜ける方法です。見つけた位置を戻り値として返せるので、フラグもラベルも要らなくなります。ループの入れ子が深くなってきたら検討するとよい形です。
whileループの応用例
Iteratorで取り出しながら削除する
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class IteratorLoop {
public static void main(String[] args) {
List<String> tasks = new ArrayList<>(List.of("掃除", "(済)洗濯", "買い物", "(済)料理"));
Iterator<String> it = tasks.iterator();
while (it.hasNext()) {
String t = it.next();
if (t.startsWith("(済)")) {
it.remove(); // ループ中の削除は Iterator 経由で
}
}
System.out.println(tasks);
}
}
[掃除, 買い物]
hasNext() がtrueの間くり返す形は、while文の代表的な使い方です。
テキストを1行ずつ読む
import java.io.BufferedReader;
import java.io.IOException;
import java.io.StringReader;
public class ReadLines {
public static void main(String[] args) throws IOException {
String text = "1行目\n2行目\n3行目";
try (BufferedReader reader = new BufferedReader(new StringReader(text))) {
String line;
int no = 0;
while ((line = reader.readLine()) != null) { // 代入と判定を同時に行う
no++;
System.out.println(no + ": " + line);
}
}
}
}
1: 1行目
2: 2行目
3: 3行目
(line = reader.readLine()) != null は、読み込んだ結果をlineに代入し、その値がnullでないかを同じ式で確かめる書き方です。代入を丸かっこで囲むのがポイントです。ファイルから読む場合は、StringReader の部分を Files.newBufferedReader(Path.of("ファイル名")) に置き換えます。
数値の桁を順に取り出す
public class Digits {
public static void main(String[] args) {
int n = 90417;
int rest = n;
int sum = 0;
int digits = 0;
while (rest > 0) {
sum += rest % 10; // 一の位を足す
rest /= 10; // 一の位を落とす
digits++;
}
System.out.println(n + " は " + digits + " 桁、各桁の合計は " + sum);
}
}
90417 は 5 桁、各桁の合計は 21
何桁あるかは数値によって変わるので、回数を先に決めないwhile文が合います。
条件が1回多いときの例外
配列を添字でたどるとき、条件を <= にすると1回多く回り、範囲外の要素を読もうとして例外になります。
public class OffByOne {
public static void main(String[] args) {
String[] names = {"A", "B", "C"};
int i = 0;
while (i <= names.length) { // <= だと1回多い
System.out.println(names[i]);
i++;
}
}
}
A
B
C
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 3 out of bounds for length 3
at OffByOne.main(OffByOne.java:6)
添字は0から始まり、最後は「長さ-1」です。条件は i < names.length と書きます。 全要素を順に見るだけなら、拡張for文にすると添字そのものを書かずに済みます。
よくある質問
Q. while文とfor文はどちらを使えばよいですか? A. くり返す回数や範囲が先に決まっているならfor文、決まっていないならwhile文が目安です。どちらでも同じ処理を書けますが、for文は初期化・条件・更新が1行にまとまるため、更新の書き忘れが起きにくくなります。ループの後でカウンタの値を使いたいときはwhile文が自然です。
Q. do-while文はどんなときに使いますか? A. 条件を確かめる前に、本体を必ず1回実行したいときです。入力のやり直し、メニューの表示、目的の値が出るまでの試行などが当てはまります。while文で書いていて「同じ処理をループの前と中に2回書いている」と気づいたら、do-while文に置き換えられます。
Q. 無限ループになったプログラムはどう止めますか? A. ターミナルならCtrl + C、IDEならコンソールの停止ボタンで止められます。止めた後は、条件に使っている変数が本体の中で変わっているか、whileの行末にセミコロンがないか、continueが更新より前にないかを順に確かめます。
Q. while (true) は使ってもよいですか? A. 使えます。出口のbreakやreturnが本体の中にはっきり書かれていれば、意図の伝わる書き方です。breakを置かないまま後ろに文を書くと、unreachable statementのコンパイルエラーになります。
まとめ
- while文は条件を先に確かめ、本体は0回以上。do-while文は本体を先に実行し、1回以上動く。
- 回数が決まっていればfor文、決まっていなければwhile文、まず1回実行したいならdo-while文を選ぶ。
- breakはループを抜け、continueは次の周へ進む。while文ではcontinueより前に更新を書く。
- 更新の書き忘れは「同じ出力が続く」、条件の後ろのセミコロンは「何も表示されず止まらない」という形で現れる。
while (true)の後ろにbreakなしで文を書くと、unreachable statementのコンパイルエラーになる。- 二重ループを外側ごと抜けるには、フラグ変数・ラベル付きbreak・メソッド化してreturnの3通りがある。
- do-while文の条件式で使う変数は、doの前で宣言する。
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「Java while文とdo-whileの使い方|breakと無限ループ」
株式会社RJCでは、Javaのwhile文やdo-while文といった基本構文を仲間と学び合っています。こうした知識を共有しながら、日々技術を磨いています。
株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。