Javaプログラミングにおいて、繰り返し処理はアルゴリズムを構築する上で最も基本的かつ重要な要素の一つです。

その中でも「for文」は、指定した回数だけ処理を繰り返す際に非常に強力なツールとなります。

Javaの進化に伴い、初期のバージョンから存在する基本のfor文に加え、コレクションの操作を容易にする「拡張for文」、さらにはJava 8以降で導入された「Stream API」による内部イテレーションなど、用途に合わせた多様な記述方法が提供されるようになりました。

本記事では、Javaにおけるfor文の基本から応用、そしてモダンな開発における最適な使い分けまでを徹底的に解説します。

Javaにおけるfor文の基本構造

Javaのfor文は、特定の回数だけ処理を繰り返したい場合や、配列のインデックスを順番に操作したい場合に適した構文です。

ループの制御変数、継続条件、更新処理を1行にまとめて記述できる点が、while文などの他のループ構文と比較した際の特徴となります。

基本のfor文の書き方

基本のfor文は、以下の構文で定義されます。

Java
for (初期化式; 条件式; 変化式) {
    // 繰り返し実行する処理
}
初期化式

ループを開始する前に一度だけ実行されます。

通常はカウンタ変数の宣言と初期化を行います。

条件式

各ループの開始前に評価されます。

この結果がtrueである限り、ループは継続されます。

変化式

ループ内の処理が終わるたびに実行されます。

一般的にはカウンタ変数をインクリメント(加算)またはデクリメント(減算)します。

基本のfor文の使用例

以下のプログラムは、0から4までの数値を順番に出力する単純な例です。

Java
public class Main {
    public static void main(String[] args) {
        // 0から4まで5回繰り返す
        for (int i = 0; i < 5; i++) {
            System.out.println("現在の値は: " + i);
        }
    }
}
実行結果
現在の値は: 0
現在の値は: 1
現在の値は: 2
現在の値は: 3
現在の値は: 4

この例では、int i = 0 で変数を初期化し、i < 5 という条件が満たされている間、ブロック内の処理を実行します。

毎回の処理終了時に i++ が実行され、値が増加していきます。

配列とfor文の組み合わせ

for文の最も一般的な用途の一つが、配列の要素を順番に処理することです。

配列の要素数を示す length プロパティを条件式に利用することで、動的な要素数にも対応可能です。

Java
public class ArrayLoop {
    public static void main(String[] args) {
        String[] fruits = {"Apple", "Banana", "Orange", "Grape"};

        // 配列の要素をインデックスを使って取得
        for (int i = 0; i < fruits.length; i++) {
            System.out.println("インデックス " + i + ": " + fruits[i]);
        }
    }
}
実行結果
インデックス 0: Apple
インデックス 1: Banana
インデックス 2: Orange
インデックス 3: Grape

配列のインデックスは0から始まるため、条件式を i <= fruits.length とすると ArrayIndexOutOfBoundsException が発生することに注意してください。

ループの制御:breakとcontinue

複雑なロジックを実装する際、特定の条件下でループを中断したり、現在の回をスキップして次の回に進んだりする必要があります。

そのために使用されるのが break 文と continue 文です。

breakによるループの強制終了

break を使用すると、条件式がまだ true であっても、直近のループから即座に脱出します。

Java
public class BreakExample {
    public static void main(String[] args) {
        for (int i = 1; i <= 10; i++) {
            if (i == 6) {
                System.out.println("6に達したためループを終了します。");
                break;
            }
            System.out.print(i + " ");
        }
    }
}
実行結果
1 2 3 4 5 6に達したためループを終了します。

continueによる現在のステップのスキップ

continue を使用すると、ループの残りの処理をスキップし、次の繰り返し(変化式の実行と条件評価)へ移行します。

Java
public class ContinueExample {
    public static void main(String[] args) {
        for (int i = 1; i <= 5; i++) {
            if (i == 3) {
                // 3の時だけスキップ
                continue;
            }
            System.out.print(i + " ");
        }
    }
}
実行結果
1 2 4 5

二重ループと多次元配列の操作

for文の中にさらに別のfor文を記述することを「二重ループ(多重ループ)」と呼びます。

これは、二次元配列の操作や、九九の表のような行列計算に頻繁に使われます。

Java
public class MultiplicationTable {
    public static void main(String[] args) {
        // 九九の表を作成
        for (int i = 1; i <= 3; i++) {
            for (int j = 1; j <= 3; j++) {
                System.out.print(i * j + "\t");
            }
            System.out.println(); // 段が変わるごとに改行
        }
    }
}
実行結果
1	2	3	
2	4	6	
3	6	9

二重ループを使用する際は、内側のループがすべて終了してから、外側のループが次のステップへ進むという流れを正確に把握しておく必要があります。

また、ループが深くなりすぎるとコードの可読性が著しく低下するため、必要に応じてメソッド化するなどの工夫が求められます。

拡張for文(Enhanced for loop)

Java 5から導入された「拡張for文」は、配列や Iterable インターフェースを実装したコレクション(List, Setなど)の全要素を走査するために最適化された構文です。

拡張for文の基本構文

Java
for (要素の型 変数名 : 配列またはコレクション) {
    // 処理
}

拡張for文の最大のメリットは、インデックスの管理が不要になり、コードが非常にシンプルになることです。

拡張for文の具体的な使用例

Java
import java.util.ArrayList;
import java.util.List;

public class EnhancedForExample {
    public static void main(String[] args) {
        List<String> cities = new ArrayList<>();
        cities.add("Tokyo");
        cities.add("Osaka");
        cities.add("Nagoya");

        // 拡張for文でリストを走査
        for (String city : cities) {
            System.out.println("都市名: " + city);
        }
    }
}
実行結果
都市名: Tokyo
都市名: Osaka
都市名: Nagoya

拡張for文の制限事項

非常に便利な拡張for文ですが、以下のようなケースでは基本のfor文を使用する必要があります。

ケース適した構文理由
要素のインデックスが必要な場合基本のfor文拡張for文では現在のインデックスを取得できない。
要素を逆順に処理したい場合基本のfor文拡張for文は常に先頭から順に処理する。
ループ中に要素を削除・置換したい場合基本のfor文 または Iterator拡張for文内でリストの構造を変更すると例外が発生しやすい。
複数の配列を同時に操作したい場合基本のfor文インデックスを同期させる必要があるため。

特に、拡張for文の中でリストから要素を削除しようとすると、ConcurrentModificationException がスローされるリスクがあるため注意してください。

Java 8以降の潮流:forEachとStream API

Java 8で導入された「Stream API」と forEach メソッドは、現代的なJava開発において欠かせない手法です。

これらは「内部イテレーション」と呼ばれ、開発者がループの制御(どのように繰り返すか)を書くのではなく、データに対して何をするかを記述する宣言的なスタイルを提供します。

List.forEachメソッド

コレクションに対して直接 forEach を呼び出し、ラムダ式を渡すことで処理を記述できます。

Java
import java.util.List;

public class ListForEach {
    public static void main(String[] args) {
        List<Integer> numbers = List.of(10, 20, 30);

        // ラムダ式を用いた繰り返し
        numbers.forEach(n -> System.out.println("値: " + n));
        
        // メソッド参照を用いた記述(より簡潔)
        numbers.forEach(System.out::println);
    }
}

Stream APIによる高度な操作

Stream APIを使用すると、要素のフィルタリング(抽出)や変換を行った上で、最終的な処理を行うことができます。

Java
import java.util.List;
import java.util.stream.Collectors;

public class StreamExample {
    public static void main(String[] args) {
        List<String> names = List.of("Alice", "Bob", "Charlie", "David");

        // "C"で始まる名前だけを抽出して大文字に変換し出力
        names.stream()
            .filter(name -> name.startsWith("C"))
            .map(String::toUpperCase)
            .forEach(System.out::println);
    }
}
実行結果
CHARLIE

for文とStream APIの使い分け

Stream APIは非常に強力ですが、常に従来のfor文を置き換えるものではありません。

for文(基本・拡張)を使うべきケース

ループ内でChecked Exceptionをスローする必要がある場合、ループの途中でreturnbreakcontinueを使って細かく制御したい場合、およびパフォーマンスが極限まで求められる単純な配列操作(Streamのオーバーヘッドを避けたい場合)。

Stream APIを使うべきケース

フィルタリング、マッピング、集計(合計・平均)などのデータ加工が主な目的である場合、並列処理(parallelStream)を容易に導入したい場合、および関数型プログラミングのスタイルで可読性を高めたい場合。

無限ループとその回避

条件式を誤ると、ループがいつまでも終了しない「無限ループ」に陥ることがあります。

基本のfor文では、以下のように記述すると無限ループになります。

Java
// 条件式を省略するとtrueとみなされ、無限ループになる
for (;;) {
    // 永久に実行される
    // 特定の条件でbreakしない限り止まらない
}

意図的な無限ループは、サーバーの待機処理などで利用されることがありますが、一般的なビジネスロジックではバグの原因となります。

無限ループを防ぐためには、以下の点を確認してください。

  • 変化式(i++ など)を書き忘れていないか。
  • 条件式が常に true になる論理構成になっていないか(例:正の数に向かうべきカウンタが減算されているなど)。

実践的なTips:パフォーマンスと可読性

リストのサイズ取得について

基本のfor文で List.size() を条件式に使う場合、JavaのコンパイラやJITコンパイラが最適化してくれることが多いため、基本的にはループの外で変数に代入しておく必要はありません。

しかし、メソッド内でリストのサイズが動的に変わらないことが明らかな場合、以下のように記述することもあります。

Java
// 一般的な書き方(これで十分な場合が多い)
for (int i = 0; i < list.size(); i++) { ... }

// わずかな最適化(古い慣習だが意味はある)
for (int i = 0, size = list.size(); i < size; i++) { ... }

nullチェックの重要性

拡張for文やStream APIを配列やコレクションに対して適用する場合、対象のオブジェクトが null であると NullPointerException が発生します。

ループに入る前に、必ず nullチェックや Optional によるハンドリングを行う癖をつけましょう。

Java
List<String> items = getItems(); // nullを返す可能性があるメソッド
if (items != null) {
    for (String item : items) {
        // 安全に処理
    }
}

まとめ

Javaにおけるfor文は、プログラムの制御フローを司る極めて重要な構文です。

基本のfor文

回数指定やインデックス操作が必要な場面で最も信頼できる選択肢です。

拡張for文

コレクションや配列の全要素を安全かつ簡潔に読み取るためのデファクトスタンダードです。

Stream API

データのフィルタリングや変換を伴う複雑な集合操作を、モダンかつ宣言的に記述するのに適しています。

それぞれの特徴を理解し、現場のコーディング規約やパフォーマンス要件、そして何より「コードの読みやすさ」を考慮して適切に使い分けることが、熟練したJavaエンジニアへの第一歩となります。

最新のJavaでは関数型プログラミングの要素が強まっていますが、根底にあるfor文の仕組みを正しく理解しておくことで、より高度な機能も自信を持って使いこなせるようになるでしょう。