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期連続の黒字決算。

ABOUT RJC RJCがどんな会社か知る 考え方、研修、働き方、福利厚生、社員の前職まで。RJCのことが丸わかり! RJC丸わかりページへ JOB DESCRIPTION 仕事内容・待遇を見てみる 仕事内容、給与・待遇、選考の流れ。経験者も未経験も!応募前に知りたいこと、まとめました! 募集要項を見る ENTRY エントリーする エントリーは1〜2分・履歴書不要です。まずは話を聞いてみたい、という方でも歓迎です! エントリーフォームへ

RJCで一緒に開発しながら、 成長を楽しみませんか?