Java Tips | コレクション:インデックス付きループ

Java Java
スポンサーリンク

インデックス付きループは「位置情報をちゃんと意識して回す」技

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()) で“インデックスの流れ”を作り、mapToObjIndexValue に変換している」ことです。
これで「インデックス付きの 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 文が何度も出てきているなら、
それを一度「インデックス付きループユーティリティ」にまとめられないか眺めてみてください。

その小さな整理が、
「位置情報も含めて、コレクションを気持ちよく扱えるエンジニア」への、
確かな一歩になります。

タイトルとURLをコピーしました