- JAVA
Java for文と拡張for文の使い方|break・continue・二重ループ
Javaのfor文とは、「初期化・条件・更新」の3つを1行にまとめて、決まった回数だけ処理を繰り返す構文です。 配列やリストの全要素を順に取り出すだけなら、添字を書かずに済む拡張for文(for-each文)が使えます。途中でやめたいときはbreak、その回だけ飛ばしたいときはcontinueを使います。
この記事では、掲載したすべてのコードをOpenJDK 21.0.10で実行し、出力をそのまま載せています。 基本の書き方に加えて、二重ループ(九九・2次元配列)、ラベル付きbreak/continueでの脱出、拡張for文の中で要素を削除したときのConcurrentModificationExceptionの再現と直し方、<=と<の1回ずれ(off-by-one)や無限ループの再現まで、つまずきやすい箇所を実際のエラー文とあわせて確認できます。
Java for文・拡張for文の早見表
やりたいこと別の書き方
| やりたいこと | 書き方 | ポイント |
|---|---|---|
| n回繰り返す | for (int i = 0; i < n; i++) { } |
0からn-1までのn回 |
| 1からnまで | for (int i = 1; i <= n; i++) { } |
nを含める |
| 配列を添字つきで回す | for (int i = 0; i < arr.length; i++) { } |
条件は<(<=は範囲外になる) |
| 全要素を順に取り出す | for (String s : list) { } |
拡張for文。添字は使えない |
| 逆順に回す | for (int i = n - 1; i >= 0; i--) { } |
開始は長さ - 1 |
| 2つ飛ばし | for (int i = 0; i <= 10; i += 2) { } |
更新はi += 2 |
| 変数を2つ使う | for (int l = 0, r = n - 1; l < r; l++, r--) { } |
同じ型ならカンマで並べる |
| ループを途中で終える | break; |
いちばん内側のループを抜ける |
| その回だけ飛ばす | continue; |
次の回へ進む |
| 二重ループを一気に抜ける | outer: for (...) { for (...) { break outer; } } |
ラベル付きbreak |
| 外側の次の回へ進む | continue outer; |
ラベル付きcontinue |
| ループ中に要素を削除 | list.removeIf(x -> 条件); |
拡張for内のremoveは例外になる |
| Mapを回す | for (var e : map.entrySet()) { } |
getKey()とgetValue() |
| 意図した無限ループ | for (;;) { ... break; } |
while (true)と同じ |
for・拡張for・forEach・while の使い分け
| 構文 | 向いている場面 | 添字 | break / continue | 要素の削除 | 配列への書き込み |
|---|---|---|---|---|---|
for (int i = 0; ...) |
回数が決まっている、添字が要る、逆順・飛ばし | 使える | 使える | 逆順なら可 | できる |
拡張for for (T x : xs) |
全要素を順に読むだけ | 使えない(カウンタ変数を足す) | 使える | 例外になる | できない |
xs.forEach(x -> ...) |
1行で済む処理、メソッド参照 | 使えない | 使えない(returnがcontinue相当) |
例外になる | できない |
while (条件) |
回数が決まっていない繰り返し | 自分で用意 | 使える | Iteratorと組み合わせる | できる |
do { } while (条件) |
最低1回は動かしたい | 自分で用意 | 使える | — | できる |
迷ったら「全要素を読むだけなら拡張for文、添字・逆順・書き換えが要るなら通常のfor文」と覚えておくと選びやすくなります。
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「Java for文と拡張for文の使い方|break・continue・二重ループ」
動作確認した環境
| 項目 | 内容 |
|---|---|
| JDK | OpenJDK 21.0.10(LTS) |
| 実行方法 | java ファイル名.java(単一ファイル実行。コンパイルと実行を一度に行う) |
| 文字コード | ソースはUTF-8。出力の日本語が「?」になる環境ではjava -Dstdout.encoding=UTF-8 ファイル名.java |
以降のコードは、クラス名と同じ名前のファイル(例:ForBasic.java)に保存して、そのまま実行できます。
for文の基本の書き方
for文はfor (初期化; 条件; 更新) { 処理 }の形で書きます。 各部分の役割と動く順番は次のとおりです。
| 部分 | 役割 | いつ動くか |
|---|---|---|
初期化 int i = 0 |
カウンタ変数を用意する | 最初に1回だけ |
条件 i < 5 |
続けるかどうかを判定する | 毎回、処理の前 |
更新 i++ |
カウンタを進める | 毎回、処理の後 |
public class ForBasic {
public static void main(String[] args) {
// 基本形: for (初期化; 条件; 更新)
for (int i = 0; i < 5; i++) {
System.out.print(i + " ");
}
System.out.println();
// 合計を求める(1〜10)
int sum = 0;
for (int i = 1; i <= 10; i++) {
sum += i;
}
System.out.println("合計: " + sum);
// 配列を添字で回す
String[] tasks = {"設計", "実装", "テスト"};
for (int i = 0; i < tasks.length; i++) {
System.out.println(i + ": " + tasks[i]);
}
}
}
実行結果:
0 1 2 3 4
合計: 55
0: 設計
1: 実装
2: テスト
条件は「処理の前」に判定されます。 最初から条件がfalseなら、処理は1回も動きません。
カウンタ変数はfor文の中だけで使える
for文の初期化で宣言した変数は、そのfor文の中だけで有効です(スコープ)。 ループの外で使おうとするとコンパイルエラーになります。
for文の閉じかっこの後(5行目)にSystem.out.println(i);と書いて実行した結果です。
実行結果(コンパイルエラー):
Scope.java:5: error: cannot find symbol
System.out.println(i);
^
symbol: variable i
location: class Scope
1 error
error: compilation failed
ループの後でも値を使いたいときは、for文より前で変数を宣言します(int i; for (i = 0; ...))。逆に言えば、for文の中で宣言しておけば、別のループで同じiという名前を何度でも使えます。
拡張for文(for-each文)の書き方
拡張for文はfor (型 変数 : 配列やコレクション) { }の形で、先頭から順に全要素を取り出します。 添字の管理が不要なので、範囲を1つ読み違える心配がありません。Java 5から使える書き方です。
import java.util.List;
import java.util.Map;
public class ForEach {
public static void main(String[] args) {
// 配列
int[] stocks = {12, 0, 7};
for (int n : stocks) {
System.out.print(n + " ");
}
System.out.println();
// List
List<String> members = List.of("佐藤", "鈴木", "高橋");
for (String name : members) {
System.out.println("会員: " + name);
}
// Map は entrySet() を回す
Map<String, Integer> items = Map.of("ペン", 120);
for (Map.Entry<String, Integer> e : items.entrySet()) {
System.out.println(e.getKey() + " = " + e.getValue());
}
// var で型を省略
for (var name : members) {
System.out.print(name.length() + " ");
}
System.out.println();
}
}
実行結果:
12 0 7
会員: 佐藤
会員: 鈴木
会員: 高橋
ペン = 120
2 2 2
拡張for文で回せるのは、配列と、Iterableを実装したクラス(List・Set・Queueなど)です。Mapはそのままでは回せないので、entrySet()・keySet()・values()のいずれかを使います。
| 回したいもの | 書き方 |
|---|---|
| 配列 | for (int n : arr) |
| List・Set | for (String s : list) |
| Mapのキーと値 | for (Map.Entry<K, V> e : map.entrySet()) |
| Mapのキーだけ | for (K key : map.keySet()) |
| Mapの値だけ | for (V value : map.values()) |
| 文字列の1文字ずつ | for (char c : str.toCharArray()) |
拡張for文の変数に代入しても、配列は変わらない
拡張for文の変数は「要素のコピー」を受け取っているため、代入しても元の配列は変わりません。 配列の中身を書き換えるときは、添字つきのfor文を使います。
import java.util.Arrays;
public class ForEachNoWrite {
public static void main(String[] args) {
int[] prices = {100, 200, 300};
// 拡張forの変数に代入しても、配列は変わらない
for (int p : prices) {
p = p * 2;
}
System.out.println("拡張for後: " + Arrays.toString(prices));
// 配列を書き換えるときは添字つきの for
for (int i = 0; i < prices.length; i++) {
prices[i] = prices[i] * 2;
}
System.out.println("for後 : " + Arrays.toString(prices));
}
}
実行結果:
拡張for後: [100, 200, 300]
for後 : [200, 400, 600]
なお、要素がオブジェクトの場合は、変数は同じオブジェクトを指しているので、item.setName("...")のようにオブジェクトの中身を変更するメソッド呼び出しは反映されます。 反映されないのは「変数そのものへの代入」です。
逆順・2つ飛ばし・複数変数の書き方
通常のfor文は、初期化・条件・更新を自由に書けるので、逆順・飛ばし・複数変数に対応できます。
public class ForVariations {
public static void main(String[] args) {
// 逆順(大きい方から)
for (int i = 5; i >= 1; i--) {
System.out.print(i + " ");
}
System.out.println();
// 配列を後ろから
String[] logs = {"A", "B", "C"};
for (int i = logs.length - 1; i >= 0; i--) {
System.out.print(logs[i] + " ");
}
System.out.println();
// 2つ飛ばし
for (int i = 0; i <= 10; i += 2) {
System.out.print(i + " ");
}
System.out.println();
// 複数の変数(左右から中央へ)
for (int left = 0, right = 4; left < right; left++, right--) {
System.out.println("left=" + left + ", right=" + right);
}
// 小数の刻みは整数カウンタから計算する
for (int i = 0; i <= 4; i++) {
double x = i * 0.25;
System.out.print(x + " ");
}
System.out.println();
// char でも回せる
for (char c = 'a'; c <= 'e'; c++) {
System.out.print(c);
}
System.out.println();
}
}
実行結果:
5 4 3 2 1
C B A
0 2 4 6 8 10
left=0, right=4
left=1, right=3
0.0 0.25 0.5 0.75 1.0
abcde
- 複数変数は同じ型に限ります。
int i = 0, long j = 0のように型を混ぜることはできません。型が違う変数を使うときは、片方をfor文の外で宣言します。 - 小数のカウンタ(
double x = 0; x <= 1.0; x += 0.1)は誤差で回数がずれることがあります。 上の例のように整数のカウンタから計算すると、回数が確実に決まります。 - 逆順の条件は
i >= 0です。i > 0にすると先頭(添字0)が処理されません。
breakとcontinueの違い
breakは「ループそのものを終える」、continueは「その回の残りを飛ばして次の回へ進む」命令です。
| 命令 | 動き | その後に進む場所 | 向いている場面 |
|---|---|---|---|
break |
ループを終了する | ループの次の文 | 見つかった時点で探すのをやめる |
continue |
その回の残りを飛ばす | for文は更新式(i++)、拡張for文は次の要素 |
対象外のデータを読み飛ばす |
return |
メソッドごと終了する | 呼び出し元 | 結果が決まったらすぐ返す |
public class BreakContinue {
public static void main(String[] args) {
int[] stocks = {5, 3, 0, 8, 0, 2};
// break: 最初に在庫 0 を見つけたらループを終える
for (int i = 0; i < stocks.length; i++) {
if (stocks[i] == 0) {
System.out.println("最初の在庫切れは index " + i);
break;
}
System.out.println("確認: index " + i);
}
// continue: 在庫 0 を飛ばして合計する
int total = 0;
for (int s : stocks) {
if (s == 0) {
continue; // この回の残りを飛ばして次の要素へ
}
total += s;
System.out.print(s + " ");
}
System.out.println("=> 合計 " + total);
}
}
実行結果:
確認: index 0
確認: index 1
最初の在庫切れは index 2
5 3 8 2 => 合計 18
for文でcontinueを使っても、更新式(i++)は必ず動きます。 一方、while文でcontinueを使うと更新の行まで飛ばしてしまうことがあるため、カウンタを使う繰り返しはfor文で書くと安心です。
二重ループの書き方(九九・2次元配列)
二重ループは、for文の中にfor文を書く形です。外側が1回進むたびに、内側が最初から最後まで回ります。 外側が9回・内側が9回なら、処理は81回動きます。
public class Nested {
public static void main(String[] args) {
// 九九(1〜9の段)
for (int i = 1; i <= 9; i++) {
for (int j = 1; j <= 9; j++) {
System.out.printf("%3d", i * j);
}
System.out.println();
}
// 2次元配列(行の長さが違ってもよい)
int[][] seats = {
{1, 2, 3},
{4, 5},
{6, 7, 8, 9}
};
for (int r = 0; r < seats.length; r++) {
for (int c = 0; c < seats[r].length; c++) {
System.out.print("[" + r + "][" + c + "]=" + seats[r][c] + " ");
}
System.out.println();
}
// 拡張forで2次元配列
int sum = 0;
for (int[] row : seats) {
for (int v : row) {
sum += v;
}
}
System.out.println("合計: " + sum);
}
}
実行結果:
1 2 3 4 5 6 7 8 9
2 4 6 8 10 12 14 16 18
3 6 9 12 15 18 21 24 27
4 8 12 16 20 24 28 32 36
5 10 15 20 25 30 35 40 45
6 12 18 24 30 36 42 48 54
7 14 21 28 35 42 49 56 63
8 16 24 32 40 48 56 64 72
9 18 27 36 45 54 63 72 81
[0][0]=1 [0][1]=2 [0][2]=3
[1][0]=4 [1][1]=5
[2][0]=6 [2][1]=7 [2][2]=8 [2][3]=9
合計: 45
二重ループで押さえておきたい点は次の3つです。
- 内側の条件は
seats[r].lengthにします。 Javaの2次元配列は「配列の配列」なので、行ごとに長さが違ってもかまいません。seats[0].lengthに固定すると、短い行で範囲外になります。 - カウンタ変数の名前を分けます。
iとj、またはr(row)とc(column)のように意味のある名前にすると、内側で外側の変数を更新してしまう取り違えが減ります。 - 拡張for文で書くと、外側の変数の型は
int[](1行分の配列)になります。 添字が要らないなら、この書き方がいちばん短くなります。
ラベル付きbreak・continueで二重ループを抜ける
ふつうのbreakは「いちばん内側のループ」だけを抜けます。外側のループごと抜けたいときは、ループにラベルを付けてbreak ラベル名;と書きます。
public class LabelBreak {
public static void main(String[] args) {
int[][] grid = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 9}
};
int target = 5;
// ラベルなし break: 内側のループだけ抜ける
System.out.println("--- ラベルなし break ---");
for (int r = 0; r < grid.length; r++) {
for (int c = 0; c < grid[r].length; c++) {
if (grid[r][c] == target) {
System.out.println("発見 [" + r + "][" + c + "]");
break;
}
}
System.out.println("行 " + r + " を確認");
}
// ラベル付き break: 外側のループごと抜ける
System.out.println("--- ラベル付き break ---");
outer:
for (int r = 0; r < grid.length; r++) {
for (int c = 0; c < grid[r].length; c++) {
if (grid[r][c] == target) {
System.out.println("発見 [" + r + "][" + c + "]");
break outer;
}
}
System.out.println("行 " + r + " を確認");
}
System.out.println("検索終了");
// ラベル付き continue: 外側の次の回へ進む
System.out.println("--- ラベル付き continue ---");
int[][] rows = {
{3, 8, 2},
{4, -1, 9},
{6, 1, 5}
};
rowLoop:
for (int r = 0; r < rows.length; r++) {
int sum = 0;
for (int v : rows[r]) {
if (v < 0) {
System.out.println("行 " + r + " は負の値があるので飛ばす");
continue rowLoop;
}
sum += v;
}
System.out.println("行 " + r + " の合計 = " + sum);
}
}
}
実行結果:
--- ラベルなし break ---
行 0 を確認
発見 [1][1]
行 1 を確認
行 2 を確認
--- ラベル付き break ---
行 0 を確認
発見 [1][1]
検索終了
--- ラベル付き continue ---
行 0 の合計 = 13
行 1 は負の値があるので飛ばす
行 2 の合計 = 12
出力から読み取れるポイントは次のとおりです。
- ラベルなしの
breakでは、5を見つけた後も行1・行2の確認が続いています。 内側のループを抜けただけで、外側は回り続けるためです。 break outer;では、見つけた時点で外側のループも終わり、すぐ「検索終了」に進みます。continue rowLoop;は、内側のループを打ち切って外側の次の回(次の行)へ進みます。 行1は合計の表示まで飛ばされています。
| 書き方 | 抜ける範囲 | その後 |
|---|---|---|
break; |
いちばん内側のループ | 外側のループは続く |
break outer; |
outerを付けたループまで |
ラベルを付けたループの次の文へ |
continue; |
いちばん内側のループのその回 | 内側の次の回へ |
continue outer; |
内側のループ全体 | outerを付けたループの次の回へ |
ラベルはループの直前に名前:と書くだけです。outerやrowLoopのように、どのループかがわかる名前にします。
ラベルを使わない書き方(メソッド化・フラグ変数)
二重ループからの脱出は、探す処理をメソッドに切り出してreturnで抜ける方法もよく使われます。 「何を探しているか」がメソッド名で伝わるので、ループが長いときに読みやすくなります。
public class LabelAlt {
record Pos(int row, int col) {}
// メソッドに切り出して return で抜ける
static Pos find(int[][] grid, int target) {
for (int r = 0; r < grid.length; r++) {
for (int c = 0; c < grid[r].length; c++) {
if (grid[r][c] == target) {
return new Pos(r, c);
}
}
}
return null;
}
public static void main(String[] args) {
int[][] grid = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
System.out.println(find(grid, 5));
System.out.println(find(grid, 99));
}
}
実行結果:
Pos[row=1, col=1]
null
| 方法 | 長所 | 向いている場面 |
|---|---|---|
| ラベル付きbreak | その場で書ける。変数が増えない | 短い二重ループ、ループの後にも処理が続く |
メソッド化してreturn |
目的が名前で伝わる。テストしやすい | 探す・判定するなど結果を返す処理 |
フラグ変数(boolean foundを外側の条件に&& !foundと足す) |
ラベルを使わずに書ける | 外側の条件に組み込みたいとき |
拡張for文の中で要素を削除するとConcurrentModificationException
拡張for文でリストを回している最中に、そのリストのremoveやaddを呼ぶとConcurrentModificationExceptionが発生します。
import java.util.ArrayList;
import java.util.List;
public class Cme {
public static void main(String[] args) {
List<String> tasks = new ArrayList<>(List.of("完了", "作業中", "完了", "未着手"));
for (String t : tasks) {
if (t.equals("完了")) {
tasks.remove(t); // 拡張forの途中でリストを変更
}
}
System.out.println(tasks);
}
}
実行結果:
Exception in thread "main" java.util.ConcurrentModificationException
at java.base/java.util.ArrayList$Itr.checkForComodification(ArrayList.java:1095)
at java.base/java.util.ArrayList$Itr.next(ArrayList.java:1049)
at Cme.main(Cme.java:7)
なぜ例外になるのか
拡張for文は、内部でIterator(要素を1つずつ取り出す係)を使っています。 ArrayListは「変更された回数」を数えており、Iteratorは作られた時点の回数を覚えています。next()で次の要素を取り出すときに回数が食い違っていると、「回している途中でリストが変わった」と判断して例外を投げます。スタックトレースのcheckForComodificationがその確認です。
例外が出る行はtasks.remove(t)ではなく、次の要素を取り出すforの行(Cme.java:7)である点も読み取れます。
直し方:removeIf・Iterator・逆順for
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class CmeFix {
public static void main(String[] args) {
// 方法1: removeIf(いちばん短い)
List<String> a = new ArrayList<>(List.of("完了", "作業中", "完了", "未着手"));
a.removeIf(t -> t.equals("完了"));
System.out.println("removeIf : " + a);
// 方法2: Iterator の remove
List<String> b = new ArrayList<>(List.of("完了", "作業中", "完了", "未着手"));
for (Iterator<String> it = b.iterator(); it.hasNext(); ) {
if (it.next().equals("完了")) {
it.remove();
}
}
System.out.println("Iterator.remove : " + b);
// 方法3: 後ろから添字で回す
List<String> c = new ArrayList<>(List.of("完了", "作業中", "完了", "未着手"));
for (int i = c.size() - 1; i >= 0; i--) {
if (c.get(i).equals("完了")) {
c.remove(i);
}
}
System.out.println("逆順 for : " + c);
// 参考: 前から添字で回して削除すると、隣の要素を飛ばす
List<String> d = new ArrayList<>(List.of("完了", "完了", "作業中"));
for (int i = 0; i < d.size(); i++) {
if (d.get(i).equals("完了")) {
d.remove(i);
}
}
System.out.println("前から for : " + d);
}
}
実行結果:
removeIf : [作業中, 未着手]
Iterator.remove : [作業中, 未着手]
逆順 for : [作業中, 未着手]
前から for : [完了, 作業中]
| 方法 | 書き方 | 選ぶ基準 |
|---|---|---|
removeIf |
list.removeIf(x -> 条件) |
条件に合う要素を消すだけなら第一候補(Java 8〜) |
Iterator.remove |
it.next()の後にit.remove() |
削除とあわせて別の処理もしたいとき |
| 逆順のfor | for (int i = size - 1; i >= 0; i--) |
添字を使った処理と組み合わせたいとき |
| 新しいリストを作る | list.stream().filter(...).toList() |
元のリストを残したいとき |
最後の「前から for」は、例外は出ないものの完了が1つ残っています。 添字0を削除すると後ろの要素が前に詰まり、次のi = 1では元の添字2の要素を見ることになるため、隣の要素を1つ飛ばします。添字で削除するなら後ろから回すと、詰まる影響を受けません。
例外が出ないまま1つ飛ばすケースもある
消す要素が「最後から2番目」のときは、例外が出ずにループが早く終わります。
import java.util.ArrayList;
import java.util.List;
public class CmeSilent {
public static void main(String[] args) {
// 最後から2番目の要素を消すと、例外が出ないまま終わる
List<String> list = new ArrayList<>(List.of("A", "B", "C"));
for (String s : list) {
System.out.println("見た: " + s);
if (s.equals("B")) {
list.remove(s);
}
}
System.out.println(list);
}
}
実行結果:
見た: A
見た: B
[A, C]
Cが一度も処理されていません。Bを消してサイズが2になり、Iteratorの位置(2)と一致したため、hasNext()がfalseを返してループが終わったからです。「たまたま動いた」ように見えても要素を飛ばしているので、ループ中の削除は上の表の方法にそろえておくと安心です。
off-by-one(1回ずれ)を再現する
off-by-oneとは、繰り返しの回数や範囲が1つだけずれる間違いのことです。 多くは条件の<と<=、開始値の0と1の組み合わせで起こります。
public class OffByOne {
public static void main(String[] args) {
int[] scores = {70, 85, 90};
// < を使う(正しい範囲: 0, 1, 2)
for (int i = 0; i < scores.length; i++) {
System.out.print(scores[i] + " ");
}
System.out.println();
// <= にすると 1 回多く回り、範囲外を読む
for (int i = 0; i <= scores.length; i++) {
System.out.print(scores[i] + " ");
}
}
}
実行結果:
70 85 90
70 85 90 Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 3 out of bounds for length 3
at OffByOne.main(OffByOne.java:13)
エラー文のIndex 3 out of bounds for length 3は、「長さ3の配列(添字は0〜2)に対して添字3を読もうとした」という意味です。配列の添字は0から始まり、最後の添字はlength - 1なので、条件はi < scores.lengthにします。
開始値と条件の組み合わせで回数がどう変わるかも、4通りのfor文で実行して数えました。
実行結果:
i=0; i<10 => 10 回
i=0; i<=10 => 11 回
i=1; i<=10 => 10 回
i=1; i<10 => 9 回
| 書き方 | 回数 | iの範囲 | 使いどころ |
|---|---|---|---|
i = 0; i < n |
n回 | 0〜n-1 | 配列・リストの添字(基本形) |
i = 1; i <= n |
n回 | 1〜n | 「1件目〜n件目」のように数える |
i = 0; i <= n |
n+1回 | 0〜n | 両端を含めたいとき |
i = 1; i < n |
n-1回 | 1〜n-1 | 先頭を除きたいとき |
「0から始めたら<、1から始めたら<=」と組で覚えると、n回の繰り返しを書き間違えにくくなります。全要素を読むだけなら拡張for文にすると、範囲そのものを書かずに済みます。
無限ループになる書き方と止め方
無限ループは、条件がいつまでもtrueのままになることで起こります。 よくある原因を、打ち切り用のカウンタを付けて再現しました。
public class Infinite {
public static void main(String[] args) {
// 例1: 更新の向きが逆(i-- のまま)。安全のため 5 回で打ち切る
int guard = 0;
for (int i = 0; i < 3; i--) {
if (++guard > 5) {
System.out.println("止まらないので打ち切り: i=" + i);
break;
}
}
// 例2: for の直後にセミコロン。ループ本体が空になり、ブロックは 1 回だけ動く
int j;
for (j = 0; j < 3; j++);
{
System.out.println("1 回だけ表示: j=" + j);
}
// 例3: 意図した無限ループは for (;;) と break で書く
int n = 0;
for (;;) {
n++;
if (n == 3) break;
}
System.out.println("for(;;) を break で終了: n=" + n);
// 例4: byte のカウンタは 127 の次に -128 へ戻る
int loops = 0;
for (byte b = 0; b < 200; b++) {
if (++loops > 300) {
System.out.println("byte は 200 に届かない: b=" + b);
break;
}
}
}
}
実行結果:
止まらないので打ち切り: i=-5
1 回だけ表示: j=3
for(;;) を break で終了: n=3
byte は 200 に届かない: b=44
| 原因 | 例 | 直し方 |
|---|---|---|
| 更新の向きが逆 | for (int i = 0; i < 3; i--) |
条件と更新の向きをそろえる(i++) |
| 内側で外側の変数を更新 | 内側のfor文の更新がi++になっている |
内側はj++。変数名を意味のある名前にする |
for文の直後に; |
for (...); の後にブロック |
セミコロンを消す(この場合は無限ループではなく1回だけ動く) |
| カウンタの型が小さい | for (byte b = 0; b < 200; b++) |
カウンタはintにする |
| 条件の変数が変わらない | ループ内で条件に使う値を更新していない | 更新する行を追加する |
- 例1は、
iが0、-1、-2…と減っていくのでi < 3がずっとtrueです。 - 例2は、
for (...);のセミコロンが「空の文」としてループ本体になります。その後のブロックはループと無関係に1回だけ動くので、j=3と表示されています。 - 例4は、
byteの最大値が127なので、b++で127の次が-128に戻り、200に届きません。
ターミナルで実行中のプログラムが止まらなくなったら、Ctrl + Cで終了できます。
拡張for文でindex(添字)が必要なとき
拡張for文には添字がありません。番号が必要なときは、次の3つの方法から選びます。
import java.util.List;
import java.util.stream.IntStream;
public class WithIndex {
public static void main(String[] args) {
List<String> articles = List.of("入門", "応用", "まとめ");
// 方法1: 通常の for(添字が主役ならこれ)
for (int i = 0; i < articles.size(); i++) {
System.out.println((i + 1) + "件目: " + articles.get(i));
}
// 方法2: 拡張for + カウンタ変数
int no = 1;
for (String title : articles) {
System.out.println("No." + no + " " + title);
no++;
}
// 方法3: IntStream.range
IntStream.range(0, articles.size())
.forEach(i -> System.out.println(i + " -> " + articles.get(i)));
}
}
実行結果:
1件目: 入門
2件目: 応用
3件目: まとめ
No.1 入門
No.2 応用
No.3 まとめ
0 -> 入門
1 -> 応用
2 -> まとめ
| 方法 | 選ぶ基準 |
|---|---|
| 通常のfor文 | ArrayListや配列で、添字を使った参照(前後の要素との比較など)がある |
| 拡張for文 + カウンタ変数 | 番号は表示に使うだけ。SetやLinkedListなどget(i)が向かないコレクション |
IntStream.range |
Streamの処理につなげたい |
LinkedListはget(i)のたびに先頭からたどるため、要素数が多いと通常のfor文は時間がかかります。コレクションの種類を問わず同じ速さで回せるのは拡張for文です。
forEachメソッドとの違い
forEachは、リストやMapが持つメソッドで、ラムダ式(x -> 処理)を渡して全要素に同じ処理を行います(Java 8〜)。
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
public class ForEachMethod {
public static void main(String[] args) {
List<String> items = List.of("机", "椅子", "棚");
// ラムダ式
items.forEach(s -> System.out.println("備品: " + s));
// メソッド参照
items.forEach(System.out::println);
// Map.forEach はキーと値を同時に受け取れる
Map<String, Integer> stock = new TreeMap<>(Map.of("机", 4, "椅子", 10));
stock.forEach((k, v) -> System.out.println(k + " x " + v));
// forEach の中の return は continue と同じ動き(break はできない)
items.forEach(s -> {
if (s.equals("椅子")) return;
System.out.println("処理: " + s);
});
}
}
実行結果:
備品: 机
備品: 椅子
備品: 棚
机
椅子
棚
机 x 4
椅子 x 10
処理: 机
処理: 棚
forEachの中ではbreakと外の変数の書き換えができない
ラムダ式はループではなく「メソッドの中身」なので、breakは書けません。また、外側のローカル変数を書き換えることもできません。
List<Integer> nums = List.of(1, 2, 3);に対して、ラムダ式の中にbreakを書いた場合です。
実行結果(コンパイルエラー):
LambdaErr.java:6: error: break outside switch or loop
nums.forEach(n -> { if (n == 2) break; });
^
1 error
error: compilation failed
続いて、外側で宣言したint sum = 0;にラムダ式の中で足し込んだ場合です。
実行結果(コンパイルエラー):
LambdaErr2.java:7: error: local variables referenced from a lambda expression must be final or effectively final
nums.forEach(n -> { sum += n; });
^
1 error
error: compilation failed
| やりたいこと | forEachでは | おすすめの書き方 |
|---|---|---|
| 途中でやめる(break) | 書けない | 拡張for文でbreak |
| その回を飛ばす(continue) | return;で代用 |
拡張for文でcontinue、またはfilter |
| 合計を外の変数に足す | コンパイルエラー | 拡張for文、またはstream().mapToInt(...).sum() |
| 1行で表示・登録する | 得意 | list.forEach(System.out::println) |
途中でやめる・合計するなど「ループの外とやり取りする処理」は拡張for文、1行で済む処理はforEachと分けると、どちらも読みやすくなります。Stream APIへの書き換えは別の記事で詳しく扱っているため、ここでは違いの要点に留めます。
whileとの使い分け
回数や範囲が決まっているならfor文、「条件を満たすまで」のように回数が決まっていないならwhile文が向いています。
do-whileは判定が処理の後にあるため、最初から条件を満たしていなくても1回は動きます。 int n = 10; do { ... n++; } while (n < 10);を実行すると、処理が1回だけ動くことを確認しました。
varを使ったfor文(Java 10〜)
varは、右辺から型を推論してくれる書き方です。 for文の初期化と、拡張for文の変数のどちらでも使えます。型名が長いMap.Entry<...>で特に読みやすくなります。
import java.util.List;
import java.util.Map;
public class VarLoop {
public static void main(String[] args) {
// var(Java 10〜): 右辺から型が決まる
for (var i = 0; i < 3; i++) { // int
System.out.print(i + " ");
}
System.out.println();
Map<String, List<Integer>> scores = Map.of("佐藤", List.of(80, 92));
for (var entry : scores.entrySet()) { // Map.Entry<String, List<Integer>>
System.out.println(entry.getKey() + " -> " + entry.getValue());
}
// 変数を書き換えないことを示す final
for (final var s : List.of("x", "y")) {
System.out.print(s);
}
System.out.println();
}
}
実行結果:
0 1 2
佐藤 -> [80, 92]
xy
varでは、カンマで複数の変数を並べる書き方はできません。
for (var i = 0, j = 10; i < j; i++, j--)と書いて実行すると、次のエラーになります。
実行結果(コンパイルエラー):
VarErr.java:3: error: 'var' is not allowed in a compound declaration
for (var i = 0, j = 10; i < j; i++, j--) {
^
1 error
error: compilation failed
複数の変数を使うときはint i = 0, j = 10のように型を書きます。また、var i = 0はintになるため、longで数えたいときはvar i = 0Lと書くか、long i = 0と型を明示します。
Java 22〜25で関係する変更(公式で確認)
手元の検証環境はJava 21のため、この節の内容は公式のJEP(JDK Enhancement Proposal)とJDK 25のリリース情報で確認したもので、Java 25での実行はしていません。 for文・拡張for文・break・continueの文法そのものは、Java 21からJava 25まで変わっていません。
| バージョン | 変更 | for文への影響 |
|---|---|---|
| Java 22 | 無名変数_が正式機能に(JEP 456) |
使わない変数をfor (String _ : list)と書ける |
| Java 25 | コンパクトなソースファイルとインスタンスmainメソッドが正式機能に(JEP 512) | クラス宣言なしでvoid main() { for (...) { } }と書ける |
Java 21で無名変数を使ったコードを実行すると、次のメッセージが表示されました(Java 21ではプレビュー機能のため)。
Unnamed.java:6: error: unnamed variables are a preview feature and are disabled by default.
for (String _ : List.of("a", "b", "c")) {
^
(use --enable-preview to enable unnamed variables)
Java 21でもjava --enable-preview --source 21 Unnamed.javaと指定すると実行でき、要素数の3が表示されることを確認しました。Java 22以降ではこの指定なしで使えます(公式で確認)。
for文のチェックリスト
| 確認すること | 見るポイント |
|---|---|
条件は<か<=か |
0始まりなら<、1始まりなら<= |
| 更新の向きは条件と合っているか | 増やすならi++、減らすならi-- |
| 内側のループの変数を取り違えていないか | 内側の条件・更新がjになっているか |
2次元配列の内側はarr[r].lengthか |
行ごとに長さが違ってもよい形にする |
for文の直後に;がないか |
for (...);は空のループになる |
| ループ中にリストを変更していないか | 削除はremoveIfかIterator、または逆順 |
| 拡張for文の変数に代入していないか | 書き換えは添字つきのfor文で行う |
カウンタの型はintか |
byte・shortは上限で値が戻る |
よくある質問
Q. 拡張for文は途中から始めたり、逆順に回したりできますか?
A. 拡張for文は「先頭から順に全要素」が基本です。途中から回す場合はlist.subList(2, list.size())のように範囲を切り出してから回すか、通常のfor文を使います。逆順は通常のfor文でi--と書くのがわかりやすく、Java 21以降のListではlist.reversed()を拡張for文に渡す方法もあります。
Q. ラベル付きbreakは使わないほうがよいですか?
A. Javaの正式な文法で、二重ループを抜ける用途では読みやすい書き方です。ループが長くなって「どこへ抜けるのか」が追いにくくなってきたら、探す処理をメソッドに切り出してreturnで抜ける形にすると、目的が名前で伝わります。この記事の比較表を目安に選んでください。
Q. 拡張for文で回す対象がnullだとどうなりますか?
A. NullPointerExceptionが発生します。空のリストや長さ0の配列であれば、処理が1回も動かずに次へ進みます。「要素がない」ことはnullではなく空のリスト(List.of())で表すと、ループの前にnullチェックを書かずに済みます。
まとめ
- for文は
for (初期化; 条件; 更新)。回数や添字が決まっているときに使い、0始まりなら条件は<にします。 - 全要素を順に読むだけなら拡張for文。 添字の読み違いがなくなり、どのコレクションでも同じ書き方で回せます。
breakはループを終える、continueはその回を飛ばす。 二重ループを一気に抜けるときはbreak ラベル名;、外側の次の回へ進むときはcontinue ラベル名;です。- 拡張for文の中で
list.remove()を呼ぶとConcurrentModificationException。removeIf・Iterator.remove・逆順のfor文に書き換えます。 i <= arr.lengthは1回多く回ってArrayIndexOutOfBoundsExceptionになります。 「0始まりは<、1始まりは<=」と組で覚えます。- 無限ループの多くは、更新の向き・変数の取り違え・カウンタの型が原因です。 チェックリストで条件と更新を見直します。
forEachは1行で済む処理向き。breakや外の変数の書き換えが必要なら拡張for文を選びます。
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「Java for文と拡張for文の使い方|break・continue・二重ループ」
株式会社RJCでは、Javaのfor文や拡張for文のような基本文法を仲間と学び合っています。こうした知識を共有しながら、日々技術を磨いています。
株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。