本ページはプロモーションが含まれています

Java入門

Java拡張for文(foreach)を5分で理解!基本から応用まで解説

トム

・都内自社開発IT企業勤務/javaのバックエンドエンジニア
/java歴10年以上 ・首都圏在住30代
・資格:基本情報技術者/応用情報技術者/Java Silver/Python3エンジニア認定基礎

「Javaのループ処理、もっと簡単に書けないかな?」

「拡張for文、つまりforeachってよく聞くけど、いまいち使い方が分からない…」多くのJava初学者が同じような悩みを抱えているのを見てきました。

特に、従来のfor文に慣れていると、拡張for文(foreach)のシンプルさに逆に戸惑ってしまうことがあるようです。しかし、一度使い方をマスターすれば、あなたのJavaコードは驚くほど簡潔で、読みやすくなります。

この記事は、まさに「Javaの拡張for文、foreachについて知りたい」と考えているあなたのために書きました。

この記事を最後まで読めば、以下の悩みが解決します。

  • 拡張for文(foreach)の基本的な書き方が分かる
  • 通常のfor文との違いや、使い分けが理解できる
  • 配列やList、Mapなど、さまざまなデータ構造での応用的な使い方が身につく
  • 拡張for文を使う上での注意点や「落とし穴」を回避できる

私の実務経験に基づいた具体的なコード例を交えながら、分かりやすく解説していきます。5分ほどで読めますので、ぜひ最後までお付き合いください。

拡張for文(foreach)とは?

Javaの拡張for文は、配列やコレクション(ListやSetなど)の全要素に対して、順番に処理を繰り返すためのシンプルな構文です。多くのプログラミング言語で「foreach」ループと呼ばれる機能と同じ役割を果たします。

この構文を使う最大のメリットは、コードが非常に簡潔になり、バグが起こりにくくなる点です。

拡張for文の基本構文と読み方

まずは、基本の形を見てみましょう。構文は驚くほどシンプルです。

for (型 変数名 : 配列またはコレクション) {
    // 各要素に対する処理
}

このように書きます。読み方は「配列またはコレクションの各要素を、順番に取り出して変数に入れ、中の処理を繰り返す」と理解すると分かりやすいです。

例えば、String型の配列namesがあり、その要素を1つずつ画面に表示したい場合、以下のように書けます。

String[] names = {"Alice", "Bob", "Charlie"};

for (String name : names) {
    System.out.println(name);
}

このコードは、names配列から要素を1つずつ取り出し、nameという変数に代入して、printlnで表示する処理を繰り返します。

通常のfor文との違い

従来のfor文と比較すると、その違いは一目瞭然です。同じ処理を通常のfor文で書くと、以下のようになります。

String[] names = {"Alice", "Bob", "Charlie"};

for (int i = 0; i < names.length; i++) {
    String name = names[i];
    System.out.println(name);
}

拡張for文と比べると、カウンタ変数iの初期化、条件式、更新処理が必要で、記述量が多くなります。特に、i < names.lengthのような条件式や、names[i]といったインデックスを使った要素の取得は、タイプミスによるバグ(例えば<=にしてしまいArrayIndexOutOfBoundsExceptionが発生するなど)の原因になりがちです。

拡張for文なら、インデックスを意識する必要がないため、そのような心配がなく、より安全にコードを書けるのです。

「foreach」と呼ばれる理由

Javaの公式なドキュメントでは「拡張for文(Enhanced for Statement)」と呼ばれています。ではなぜ「foreach」という通称が広く使われているのでしょうか。

その理由は、C#やPHP、JavaScriptなど、他の多くのプログラミング言語にforeachというキーワードで同じ機能のループ構文が存在するからです。

Javaでも、その機能的な役割から「for-eachループ」や、単純に「foreach」と呼ばれるのが一般的になっています。Java開発者の間では「foreach」で話が通じることがほとんどなので、覚えておくと便利です。

拡張for文の使い方【基本編】

それでは、具体的な使い方をデータ構造ごとに見ていきましょう。基本をしっかり押さえれば、応用もすぐに理解できます。

配列に対して使う方法

最も基本的な使い方が、配列に対するループ処理です。先ほどの例でも使いましたが、int型の配列でも同様に扱えます。

// int型の配列を準備
int[] scores = {90, 85, 70, 95, 80};

// 合計点を計算する変数を準備
int sum = 0;

// 拡張for文で配列の各要素を足し合わせる
for (int score : scores) {
    sum += score;
}

System.out.println("合計点: " + sum); // 出力結果: 合計点: 420

このように、配列の全要素に対して何らかの処理(この場合は合計値を求める)を行いたい場合に非常に有効です。

Listなどコレクションでの使い方

Javaでは、ArrayListHashSetなど、コレクションフレームワークのクラスを頻繁に使います。拡張for文は、これらのコレクションに対しても全く同じように使えます。

ArrayListの例を見てみましょう。

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

public class Main {
    public static void main(String[] args) {
        List<String> fruits = new ArrayList<>();
        fruits.add("Apple");
        fruits.add("Banana");
        fruits.add("Orange");

        // Listの要素を拡張for文で取り出す
        for (String fruit : fruits) {
            System.out.println(fruit);
        }
    }
}

この拡張for文が使えるのは、ArrayListIterableというインターフェースを実装しているからです。java.lang.Iterableを実装しているクラスであれば、どんなものでも拡張for文の対象にできます。

インデックスを使いたい場合の注意点

拡張for文の大きな特徴は、インデックスを使わない手軽さです。しかし、処理によっては「今、何番目の要素を処理しているか」というインデックス番号が必要になる場面もあります。

結論から言うと、拡張for文では直接インデックスを取得できません。

どうしてもインデックスが必要な場合は、2つの対処法があります。

  1. 外部にカウンタ変数を置く
List<String> languages = new ArrayList<>();
languages.add("Java");
languages.add("Python");
languages.add("Go");

int index = 0;
for (String lang : languages) {
    System.out.println(index + ": " + lang);
    index++;
}

このようにループの外でカウンタ変数を宣言し、ループ内でインクリメント(+1)する方法です。ただし、この方法は少し冗長に見えます。

  1. 通常のfor文を使う

インデックスを使った処理がメインの場合は、無理に拡張for文を使わず、素直に通常のfor文を使った方がコードがシンプルで分かりやすくなります。

for (int i = 0; i < languages.size(); i++) {
    System.out.println(i + ": " + languages.get(i));
}

適材適所で構文を使い分けるのが、良いプログラマーのスキルです。

拡張for文の使い方【応用編】

基本を押さえたところで、もう少し複雑なデータ構造での使い方を見ていきましょう。

ネストされた配列・リストを処理する

二次元配列や、リストの中にリストが入っているような、ネスト(入れ子)構造のデータも、拡張for文をネストさせることで簡単に処理できます。

例えば、二次元配列(九九の表のようなもの)を考えてみましょう。

int[][] matrix = {
    {1, 2, 3},
    {4, 5, 6},
    {7, 8, 9}
};

// 拡張for文をネストさせる
for (int[] row : matrix) { // まず外側の配列から内側の配列(行)を取り出す
    for (int num : row) { // 次に内側の配列から各要素を取り出す
        System.out.print(num + " ");
    }
    System.out.println(); // 行ごとに改行
}

出力結果:

1 2 3 
4 5 6 
7 8 9 

外側のループで各行(int[] row)を取り出し、内側のループでその行の各要素(int num)を取り出しています。このように構造が直感的に分かりやすいのが利点です。

Map(キーと値)を拡張for文で処理する

HashMapのようなMapインターフェースを実装したクラスは、キーと値のペアでデータを保持します。MapはそのままではIterableではないため、直接拡張for文の対象にはできません。

しかし、Mapが提供する以下の3つのメソッドを使うことで、拡張for文で処理できるようになります。

  1. keySet() : キーの一覧を取得するキーだけが必要な場合に便利です。Map<String, Integer> items = new HashMap<>(); items.put("Pen", 100); items.put("Notebook", 150); for (String key : items.keySet()) { System.out.println("キー: " + key); }
  2. values() : 値の一覧を取得する値だけが必要な場合はこちらを使います。for (Integer value : items.values()) { System.out.println("値: " + value); }
  3. entrySet() : キーと値のペア(Map.Entry)の一覧を取得するキーと値の両方を同時に使いたい場合に最も効率的で、よく使われる方法です。for (Map.Entry<String, Integer> entry : items.entrySet()) { String key = entry.getKey(); Integer value = entry.getValue(); System.out.println(key + " は " + value + "円です。"); } このentrySet()を使う方法が、Mapを扱う際の定石なので、ぜひ覚えてください。

break・continueの使い方

拡張for文の中でも、通常のfor文と同じようにbreakcontinueが使えます。

  • break: ループ処理を完全に中断します。
  • continue: 現在の周の処理をスキップし、次の要素の処理に移ります。

breakの例: 配列の中から特定の数値を見つけたら処理を終了する

int[] numbers = {1, 5, -3, 8, 4};

for (int num : numbers) {
    if (num < 0) {
        System.out.println("負の数を見つけたので処理を中断します。");
        break;
    }
    System.out.println("処理した数値: " + num);
}

continueの例: 偶数だけを処理対象とする(奇数はスキップする)

for (int num : numbers) {
    if (num % 2 != 0) { // 奇数の場合はスキップ
        continue;
    }
    System.out.println("処理した偶数: " + num);
}

拡張for文の注意点と落とし穴

非常に便利な拡張for文ですが、いくつか知っておくべき注意点があります。これを知らないと、予期せぬエラーの原因となる可能性があります。

要素の削除や変更ができない理由

最も重要な注意点は、拡張for文のループ内で、ループ対象のコレクションの要素を追加したり削除したりしてはいけないというルールです。

もし実行すると、ConcurrentModificationExceptionという例外が発生します。

List<String> list = new ArrayList<>();
list.add("A");
list.add("B");
list.add("C");

// これはエラーになる!
for (String str : list) {
    if ("B".equals(str)) {
        list.remove(str); // ループ中に要素を削除しようとしている
    }
}

なぜエラーになるのでしょうか。その理由は、拡張for文の内部的な仕組みにあります。拡張for文は、コンパイルされるとIterator(イテレータ)という仕組みを使ったコードに変換されます。

Iteratorは、コレクションの状態(要素数など)を記憶しながら要素を1つずつ取り出します。ループの途中で元のコレクションが変更されると、Iteratorが記憶している状態と食い違いが生じ、安全に処理を続けられないため、例外を発生させて処理を止めるのです。これをフェイルファスト(fail-fast)と呼びます。

Iteratorとの違いを理解しよう

では、ループ中に要素を安全に削除したい場合はどうすればよいのでしょうか。その答えは、拡張for文の内部で使われているIteratorを明示的に使うことです。

Iteratorにはremove()というメソッドがあり、これを使えば安全に要素を削除できます。

List<String> list = new ArrayList<>();
list.add("A");
list.add("B");
list.add("C");

Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
    String str = iterator.next();
    if ("B".equals(str)) {
        iterator.remove(); // Iteratorのremove()メソッドを使う
    }
}

System.out.println(list); // 出力結果: [A, C]

少し記述は増えますが、ループ中の安全な要素削除にはIteratorを使う、と覚えておきましょう。

拡張for文を使うべきケース・使わない方がよいケース

最後に、どのような場面で拡張for文を使い、どのような場面で避けるべきかを整理します。

読み取り専用の処理に最適

拡張for文が最も輝くのは、配列やコレクションの全要素に対して、順番に参照(読み取り)するだけの処理です。

  • 全要素を画面に表示する
  • 全要素の合計値や平均値を計算する
  • 特定の条件に合う要素を探す

このようなケースでは、コードが簡潔で読みやすくなり、インデックスの指定ミスといったバグも防げるため、積極的に使うべきです。

ループ中に要素を変更する場合は避ける

一方で、以下のようなケースでは拡張for文は不向きです。通常のfor文やIteratorを使いましょう。

  • ループ中に要素の追加や削除を行いたい場合→ Iteratorを使いましょう。
  • 特定のインデックスの要素を直接書き換えたい場合→ 通常のfor文を使いましょう。(例:array[i] = newValue;)
  • 逆の順番で(末尾から先頭へ)処理したい場合→ 通常のfor文でカウンタをデクリメント(-1)させましょう。
  • 処理中にインデックス番号そのものが必要な場合→ 通常のfor文を使いましょう。

まとめ:拡張for文を使えばコードが読みやすくなる

この記事では、Javaの拡張for文(foreach)について、基本的な使い方から応用、そして注意点までを解説しました。

for-eachでミスを減らし、コードをシンプルに

最後に要点をまとめます。

  • 拡張for文(foreach)は、配列やコレクションの全要素を順番に処理するためのシンプルな構文です。
  • for (型 変数名 : コレクション) の形で使います。
  • 通常のfor文に比べてコードが短く、インデックス関連のバグを防げるのが大きなメリット。
  • ListSetはもちろん、MapentrySet()などを使えば処理可能です。
  • 最大の注意点は、ループ中にコレクションの要素を追加・削除できないこと。変更が必要な場合はIteratorを使いましょう。

拡張for文は、Javaのコードをより安全で、誰にとっても読みやすいものにしてくれる強力なツールです。適切に使いこなせば、あなたのプログラミングの効率と品質は間違いなく向上します。

  • この記事を書いた人
  • 最新記事

トム

・都内自社開発IT企業勤務/javaのバックエンドエンジニア
/java歴10年以上 ・首都圏在住30代
・資格:基本情報技術者/応用情報技術者/Java Silver/Python3エンジニア認定基礎

-Java入門