for-each 文の全体像
for-each(拡張 for 文)は「配列やコレクションの全要素を順に取り出して処理する」ための最短記法です。書式は for (要素の型 変数 : 配列またはIterable) { ... }。インデックス管理が不要で、境界バグ(オフバイワン)を防ぎ、読みやすさが大幅に向上します。対象は配列と Iterable(List, Set など)で、マップは entrySet() などを使って要素化します。
基本構文と動き
最小の書式(配列)
String[] names = {"sato", "suzuki", "sakai"};
for (String n : names) {
System.out.println(n);
}
Java左側の型と変数は「各要素」を受け取ります。n は読み取り用の一時変数で、ループごとに要素が代入されます。インデックスは使いません。
コレクション(List/Set)
java.util.List<Integer> list = java.util.List.of(3, 8, 2);
for (int v : list) {
System.out.println(v);
}
JavaIterable を実装するコレクションなら同様に使えます。マップは「キーと値のペア」を要素化します。
java.util.Map<String, Integer> map = java.util.Map.of("A",1, "B",2);
for (java.util.Map.Entry<String, Integer> e : map.entrySet()) {
System.out.println(e.getKey() + "=" + e.getValue());
}
Java重要ポイントの深掘り:いつ使うか・使わないか
適材適所(向いている場面)
- 全件処理: 全要素に同じ処理を行う。
- 境界安全: インデックス不要でオフバイワンを防げる。
- 読みやすさ重視: 意図(「これを順に処理」)が一目で伝わる。
不向きな場面(通常の for を選ぶ)
- インデックスが必要: 位置に応じた分岐、先頭・末尾の区別、隣接要素参照。
- 逆順処理: for-each は前からのみ。後ろから処理したいなら通常の for。
- 途中で要素を安全に削除したい: コレクションでは
Iterator.remove()が必要。for-each 中のlist.remove(...)はConcurrentModificationExceptionの原因になります。
重要ポイントの深掘り:変更と副作用
配列要素の「再代入」と「変数再代入」の違い
int[] a = {10, 20, 30};
for (int v : a) {
v = v + 1; // v は「値のコピー」。配列は変わらない
}
System.out.println(java.util.Arrays.toString(a)); // [10, 20, 30]
Javav を書き換えても配列は変わりません。要素を更新するなら添字が必要です。
for (int i = 0; i < a.length; i++) {
a[i] = a[i] + 1;
}
Java参照型の場合は「参照が渡される」ため、中身の状態を変更する操作は反映されます。
class Box { int value; }
Box[] boxes = { new Box(), new Box() };
for (Box b : boxes) {
b.value++; // 参照先の状態を変更 → 配列から見ても反映される
}
Javaコレクションの安全な削除
java.util.List<Integer> list = new java.util.ArrayList<>(java.util.List.of(3, -1, 8));
for (java.util.Iterator<Integer> it = list.iterator(); it.hasNext(); ) {
int v = it.next();
if (v < 0) it.remove(); // 安全
}
// for-each 中に list.remove(v) は危険(同時変更で例外)
Java要素の追加も同様に注意が必要です。構造変更を伴う操作は、イテレータ方式か別の収集先を使います。
2次元・マップ・ストリームとの併用
2次元配列(配列の配列)
int[][] m = {{1,2,3},{4,5,6}};
for (int[] row : m) {
for (int x : row) {
System.out.printf("%3d", x);
}
System.out.println();
}
Java外側で行、内側で各行の要素を処理します。ジャグ配列でも安全に走査できます。
マップの反復(キー、値、エントリ)
java.util.Map<String, Integer> map = java.util.Map.of("A",1, "B",2);
for (String k : map.keySet()) System.out.println("key=" + k);
for (Integer v : map.values()) System.out.println("val=" + v);
for (var e : map.entrySet()) System.out.println(e.getKey() + "=" + e.getValue());
Java用途に応じて取り出すビューを選択します。
ストリームとの関係(参考)
「加工+集計」を一行で書きたいなら stream() も選択肢です。ただし、for-each は直感的で学習コストが低く、デバッグが容易という利点があります。
制御文(break/continue)と例外安全
break/continue の使い方
for (String s : java.util.List.of("OK:1", "BAD:x", "OK:2")) {
if (!s.startsWith("OK:")) break; // ここで終了
System.out.println("process: " + s);
}
Javacontinue は現在の周回をスキップして次へ進みます。通常の for と同様に使えます。
Null と型の一致
配列・コレクションに null が含まれる場合、メソッド呼び出し時に NullPointerException を避けるため、ガードを前に置きます。要素型は宣言側と一致させ、プリミティブ配列とラッパー配列の取り違えに注意します(int[] と List<Integer> は別物)。
例題で身につける
例 1: フィルタ+集計(対象外をスキップ)
public class SumValid {
public static void main(String[] args) {
int sum = 0;
for (int v : new int[] {3, -1, 8, -5, 2}) {
if (v < 0) continue;
sum += v;
}
System.out.println(sum); // 13
}
}
Java例 2: 文字列の整形出力(最後だけ区切りを出さない)
public class PrintWithComma {
public static void main(String[] args) {
String[] a = {"Java", "is", "fun"};
for (int i = 0; i < a.length; i++) {
boolean last = (i == a.length - 1);
System.out.print(a[i]);
if (!last) System.out.print(", ");
}
}
}
Java「位置依存の処理」は通常の for を選ぶ例です。
例 3: マップのエントリ反復
import java.util.Map;
public class MapEach {
public static void main(String[] args) {
Map<String, Integer> m = Map.of("A",1, "B",2);
for (Map.Entry<String, Integer> e : m.entrySet()) {
System.out.println(e.getKey() + "=" + e.getValue());
}
}
}
Java設計の指針(重要部分のまとめ)
- 全件処理と読みやすさが最優先なら for-each。インデックスや逆順が必要なら通常の for を選ぶ。
- 配列の値更新は for-eachではできない(変数再代入は反映されない)。更新は添字で。
- コレクションの構造変更はイテレータで安全に。for-each中の
remove/addは避ける。 - 2次元は外側で行、内側で要素。マップはキー/値/エントリを明確に選ぶ。
break/continueは通常の for と同様に使える。nullを含む要素はガードで守る。
