インデックス付きループは「位置情報をちゃんと意識して回す」技
for (T x : list) の拡張 for 文はシンプルで読みやすいですが、
「何番目か(インデックス)」が欲しくなった瞬間に、ちょっと困ります。
「1行目には行番号を付けたい」
「偶数行だけ色を変えたい」
「前の要素と比較したい」
こういうときに使うのが「インデックス付きループ」です。
今日は、業務で使いやすい“インデックス付きループ用ユーティリティ”を、かみ砕いて整理します。
まずは素朴な for 文でのインデックス付きループ
List をインデックス付きで回す基本形
一番基本の形は、昔ながらの for 文です。
List<String> names = List.of("山田", "佐藤", "鈴木");
for (int i = 0; i < names.size(); i++) {
String name = names.get(i);
System.out.println(i + " : " + name);
}
Javaここでの重要ポイントは二つです。
一つ目は、「インデックス i と要素 name の両方が手に入る」ことです。
これだけで「何番目か」を使ったロジックは全部書けます。
二つ目は、「size() と get(i) を毎回書くのが、だんだんうるさくなってくる」ということです。
業務コードで何度も出てくるパターンなので、ここを“いい感じに包む”のがユーティリティの役目です。
インデックス+要素をまとめた小さなクラスを作る
IndexValue という「ペア」を用意する
まず、「インデックスと値」を一緒に持つ小さなクラスを作ります。
public final class IndexValue<T> {
private final int index;
private final T value;
public IndexValue(int index, T value) {
this.index = index;
this.value = value;
}
public int getIndex() { return index; }
public T getValue() { return value; }
}
Javaこれを使って、「List を IndexValue<T> の Stream に変換する」ユーティリティを書きます。
import java.util.List;
import java.util.stream.IntStream;
import java.util.stream.Stream;
public final class Indexed {
private Indexed() {}
public static <T> Stream<IndexValue<T>> stream(List<T> list) {
if (list == null || list.isEmpty()) {
return Stream.empty();
}
return IntStream.range(0, list.size())
.mapToObj(i -> new IndexValue<>(i, list.get(i)));
}
}
Java使い方はこうなります。
List<String> names = List.of("山田", "佐藤", "鈴木");
Indexed.stream(names)
.forEach(iv ->
System.out.println(iv.getIndex() + " : " + iv.getValue()));
Javaここでの重要ポイントは三つです。
一つ目は、「IntStream.range(0, list.size()) で“インデックスの流れ”を作り、mapToObj で IndexValue に変換している」ことです。
これで「インデックス付きの Stream」が手に入ります。
二つ目は、「IndexValue という名前付きの型にすることで、“インデックス+値”という意味がコードから読み取れる」ことです。
単なる Map.Entry<Integer, T> などより、意図がはっきりします。
三つ目は、「null や空 List のときに Stream.empty() を返している」ことです。
呼び出し側は「とりあえずインデックス付きで回す」とだけ書けばよく、null チェックを毎回書かなくて済みます。
インデックス付き forEach ユーティリティ
「Stream はまだ難しい」なら、コールバック形式にする
「Stream はまだ慣れていない」という前提なら、
“インデックス付き forEach” という形にしてしまうのもアリです。
import java.util.List;
import java.util.function.BiConsumer;
public final class Indexed {
private Indexed() {}
public static <T> void forEach(
List<T> list,
BiConsumer<Integer, T> action
) {
if (list == null || list.isEmpty()) {
return;
}
for (int i = 0; i < list.size(); i++) {
action.accept(i, list.get(i));
}
}
}
Java使い方はこうです。
List<String> names = List.of("山田", "佐藤", "鈴木");
Indexed.forEach(names, (i, name) -> {
System.out.println(i + " : " + name);
});
Javaここでの重要ポイントは二つです。
一つ目は、「BiConsumer<Integer, T> を使って、“インデックスと値を同時に受け取る処理”を渡せる」ことです。
ラムダの (i, name) -> { ... } が、とても自然な形になります。
二つ目は、「インデックス付きループの“お決まりパターン”を一箇所に閉じ込めている」ことです。for (int i = 0; i < list.size(); i++) を何度も書かなくて済み、バグの入り込む余地も減ります。
インデックスを 1 始まりで扱いたい場合
表示用と内部計算用を分ける
業務では、「画面上は 1 行目・2 行目…と表示したい」ことが多いです。
その場合、内部は 0 始まりで計算しつつ、表示だけ 1 を足すのがシンプルです。
Indexed.forEach(names, (i, name) -> {
int rowNo = i + 1; // 表示用
System.out.println(rowNo + "行目 : " + name);
});
Javaここでの重要ポイントは、
「内部のインデックスは 0 始まりで統一し、表示や外部仕様だけ 1 始まりに変換する」ことです。
内部と外部で“どちらが何始まりか”が混ざると、一気にバグりやすくなります。
「中は 0、外は 1」と決めておくと、頭の中がスッキリします。
インデックス付きループが役立つ具体例
例1:前の要素と比較する
例えば、「昇順になっているかチェックしたい」場合。
List<Integer> nums = List.of(1, 3, 2, 4);
Indexed.forEach(nums, (i, n) -> {
if (i == 0) return; // 先頭はスキップ
int prev = nums.get(i - 1);
if (prev > n) {
System.out.println("昇順崩れ: index=" + i +
" prev=" + prev + " curr=" + n);
}
});
Javaインデックスがあることで、「前の要素」「次の要素」との比較が自然に書けます。
例2:偶数行・奇数行で処理を変える
Indexed.forEach(names, (i, name) -> {
boolean isEvenRow = (i % 2 == 0); // 0,2,4,... を偶数行とみなす
if (isEvenRow) {
System.out.println("[偶数行] " + name);
} else {
System.out.println("[奇数行] " + name);
}
});
Javaここでの重要ポイントは、
「インデックスがあるだけで、“位置に応じたロジック”がシンプルに書ける」ことです。
まとめ:インデックス付きループユーティリティで身につけてほしい感覚
インデックス付きループは、
単に「for 文の書き方」ではなく、
「“何番目か”という情報を、きれいに扱う設計」です。
素朴な for 文でも書けるが、毎回 size() と get(i) を書くのはノイズになる。IndexValue<T> や BiConsumer<Integer, T> を使って、「インデックス+値」を自然に受け取れる形にする。
内部は 0 始まりで統一し、表示など外部仕様だけ 1 始まりに変換する、と決めておく。
「前後比較」「偶数行・奇数行」「行番号表示」など、“位置依存のロジック”を素直に書けるようにする。
あなたのコードのどこかに、
同じようなインデックス付き for 文が何度も出てきているなら、
それを一度「インデックス付きループユーティリティ」にまとめられないか眺めてみてください。
その小さな整理が、
「位置情報も含めて、コレクションを気持ちよく扱えるエンジニア」への、
確かな一歩になります。
