繰り返しのネストの全体像
「繰り返しのネスト」は、for/while/foreach などのループを入れ子にして、2次元以上のデータ(表、グリッド、組み合わせ)を処理する書き方です。外側のループが“行”、内側のループが“列”のように、階層ごとに役割を分けるのが基本です。強力ですが、複雑化しやすく計算量も増えるため、可読性の維持と早期終了の設計が重要になります。
基本パターンと考え方
二重ループの基本形
二重ループは「外側がひとまとまりの対象、内側がその中身」という役割分担で書きます。
int[][] grid = {
{1, 2, 3},
{4, 5, 6}
};
for (int r = 0; r < grid.length; r++) { // 行
for (int c = 0; c < grid[r].length; c++) { // 列
int v = grid[r][c];
System.out.println("[" + r + "," + c + "]=" + v);
}
}
Javaインデックスは「0 以上、サイズ未満」が大原則です。外側の要素ごとに内側の長さが違う(ジャグ配列)場合、必ず行ごとに列の長さを確認します。
foreach で安全に書く
インデックス管理を減らしたいなら foreach を使うと読みやすく、境界ミスも減ります。
for (int[] row : grid) {
for (int v : row) {
System.out.print(v + " ");
}
System.out.println();
}
Java重要ポイントの深掘り:計算量と早期終了
計算量の見積もりと限界
二重ループは概ね O(n·m)、三重ループは O(n·m·k) になります。入力サイズが増えると処理時間は掛け算で膨らむため、「本当に全探索が必要か」「前処理で高速化できないか」を最初に考えます。例えば検索なら、ハッシュ構造(Map/Set)を使うことで O(1) 近似の照合に置き換えられます。
// 二重ループの検索(遅い)
for (String a : listA) {
for (String b : listB) {
if (a.equals(b)) { /* ... */ }
}
}
// 高速化:Setへ前処理
var setB = new java.util.HashSet<>(listB);
for (String a : listA) {
if (setB.contains(a)) { /* ... */ }
}
Java早期終了とラベル付き break/continue
「条件を満たしたら即終了」できる設計にすると無駄が減ります。ネストを飛び越えるにはラベル付き break が有効です。
search:
for (int r = 0; r < grid.length; r++) {
for (int c = 0; c < grid[r].length; c++) {
if (grid[r][c] == 42) {
System.out.println("found at " + r + "," + c);
break search; // 外側ループごと終了
}
}
}
Javacontinue は「今の反復をスキップして次へ」。条件分岐を浅く保つために活用します。
よくある落とし穴と回避策
役割が曖昧なネスト
外側・内側の責務が混ざると読みにくくなります。変数名に文脈を込め、処理を小さなメソッドに分けて「一階層=一責務」にします。
for (Order order : orders) {
processOrderLines(order.lines());
}
static void processOrderLines(java.util.List<Line> lines) {
for (Line line : lines) {
applyDiscount(line);
}
}
Javaループ内の重い処理・重複計算
ループの中で毎回同じ計算や I/O を行うと爆発的に遅くなります。前計算(キャッシュ)や外出しを検討します。
// 悪い:毎回正規表現をコンパイル
for (String s : texts) {
boolean ok = java.util.regex.Pattern.compile("[A-Z]+").matcher(s).matches();
}
// 良い:事前にコンパイル
var pattern = java.util.regex.Pattern.compile("[A-Z]+");
for (String s : texts) {
boolean ok = pattern.matcher(s).matches();
}
Java変更を伴う走査の破綻
リストを走査しながら削除すると、インデックスがずれてバグになります。Iterator の remove を使うか、末尾から削除します。
var it = list.iterator();
while (it.hasNext()) {
if (shouldRemove(it.next())) it.remove();
}
Java設計の引き出し:ネストを減らすテクニック
フィルタ→変換→集計の段階化
前段で対象を絞り、後段で処理する構造にするとネストが浅くなります。
var activeUsers = new java.util.ArrayList<User>();
for (User u : users) {
if (!u.isActive()) continue; // フィルタ
activeUsers.add(u);
}
for (User u : activeUsers) {
sendMail(u); // 処理
}
Javaマップ化・グルーピング
二重ループの結合条件は「キーでまとめる」ことで 1 ループに還元できます。
var byId = new java.util.HashMap<String, User>();
for (User u : users) byId.put(u.id(), u);
for (Order o : orders) {
User u = byId.get(o.userId());
if (u != null) link(o, u);
}
Java事前ソートで二本指法(two-pointer)
両リストをソートして前から突き合わせると、二重ループでも O(n + m) に近づけられます。
var xs = new java.util.ArrayList<>(listA);
var ys = new java.util.ArrayList<>(listB);
java.util.Collections.sort(xs);
java.util.Collections.sort(ys);
int i = 0, j = 0;
while (i < xs.size() && j < ys.size()) {
int cmp = xs.get(i).compareTo(ys.get(j));
if (cmp == 0) { /* マッチ */ i++; j++; }
else if (cmp < 0) i++; else j++;
}
Java例題で身につける
例 1: 掛け算表(行×列)
public class Table {
public static void main(String[] args) {
for (int i = 1; i <= 9; i++) {
for (int j = 1; j <= 9; j++) {
System.out.print((i * j) + "\t");
}
System.out.println();
}
}
}
Java例 2: 2D グリッドで隣接セルを数える
public class Neighbors {
static int countNeighbors(int[][] g, int r, int c) {
int count = 0;
for (int dr = -1; dr <= 1; dr++) {
for (int dc = -1; dc <= 1; dc++) {
if (dr == 0 && dc == 0) continue;
int nr = r + dr, nc = c + dc;
if (nr < 0 || nc < 0 || nr >= g.length || nc >= g[nr].length) continue;
if (g[nr][nc] == 1) count++;
}
}
return count;
}
}
Java境界チェックを先に行い、早期 continue で分岐を浅く保っています。
例 3: 条件一致で早期終了(ラベル付き break)
public class Search {
public static void main(String[] args) {
int[][] g = {{1,2,3},{4,42,6}};
found:
for (int r = 0; r < g.length; r++) {
for (int c = 0; c < g[r].length; c++) {
if (g[r][c] == 42) {
System.out.println("found at " + r + "," + c);
break found;
}
}
}
}
}
Java仕上げのアドバイス(重要部分のまとめ)
ネストは「外側=まとまり、内側=その中身」で役割を分け、インデックスは 0 以上サイズ未満の原則を守る。計算量は掛け算で増えるため、早期終了・前処理(Set/Map への変換、ソートと two-pointer)で無駄を削る。重い処理はループ外へ、削除は Iterator で安全に。読みにくくなったらメソッド分割で一階層=一責務へ——この型が身につけば、ネストは強力さを保ったまま、速く安全に扱えます。
