広告

Java : 拡張for文 (for-eachループ文) の使い方

拡張for文(for-eachループ文) は、単純なコーディングミスを減らすことができる強力な文法です。
この記事では、通常の for文と比較しつつ、拡張for文の基本的な使い方についてご紹介します。

※本記事は、通常のfor文は理解しているかたを対象としています。


拡張for文

コード例

さっそく「基本for文」と「拡張for文」のコード例を見てみましょう。
それぞれの for文で、配列の全ての要素を出力させます。

final String[] strings = {"aaa", "bbb", "ccc"};

// ----------------
// 基本for文
for (int i = 0; i < strings.length; i++) {
    System.out.println(strings[i]);
}

// 結果
// ↓
//aaa
//bbb
//ccc

// ----------------
// 拡張for文
for (final String s : strings) {
    System.out.println(s);
}

// 結果
// ↓
//aaa
//bbb
//ccc

拡張for文のほうが、少しシンプルに記述できていますね。

構文

14.14.2. The enhanced for statement
EnhancedForStatement:
  for ( LocalVariableDeclaration : Expression ) Statement

上記の引用は、公式の Java言語仕様による拡張for文の構文です。

簡単に書くと

for (T value : "配列""Iterable" を実装したクラス) {
    ...
}

となります。

拡張for文で使えるオブジェクトは、配列Iterable を実装したクラス(またはインタフェース) です。
Iterable を実装した代表的なクラス(インタフェース) は List ですね。

Iterableサブクラス

T は要素の型で value は変数です。
value には、繰り返される要素が先頭から順番に代入されていきます。

以下は、List を使った例になります。

final List<String> list = List.of("aaa", "bbb", "ccc");

for (final String s : list) {
    System.out.println(s);
}

// 結果
// ↓
//aaa
//bbb
//ccc

メリット

基本for文と拡張for文の大きな違いは、条件式 があるかないかです。

例えば、下記のコード例では…

final int[] values = {10, 20, 30, 40, 50};

// 基本for文
for (int i = 0; i < values.length; i++) {
    ...
}

条件式は "i < values.length" の部分となります。

for (int i = 0; i < values.length; i++) {
                ^^^^^^^^^^^^^^^^^ <--- この部分!
    ...
}

条件式(if文基本for文while文 など) の多いプログラムは、複雑になりがちです。
分岐も多いのでテストも大変になり、不具合も多くなる … と経験的には感じています。

拡張for文は条件式がないので、次のようなメリットがあります。

  • 単純にコーディングミスが減ります。
  • for文の意図 … 全ての要素を順番に処理する ということが明確になります。

実感してもらうために、基本for文の例を5つ出してみます。

  • values 配列の全ての要素を表示させる

というのを正解とします。

何番が正しくて、何番が間違いか、ぱっと見てすぐに分かりますでしょうか。

final int[] values = {10, 20, 30, 40, 50};

// 1番
for (int i = 0; i < 5; i++) {
    System.out.println(values[i]);
}
// 2番
for (int i = 0; i <= 5; i++) {
    System.out.println(values[i]);
}
// 3番
for (int i = 0; i < values.length; i++) {
    System.out.println(values[i]);
}
// 4番
for (int i = 0; i < values.length - 1; i++) {
    System.out.println(values[i]);
}
// 5番
for (int i = 0; i <= values.length - 1; i++) {
    System.out.println(values[i]);
}

正解は、1番と3番と5番でした。

final int[] values = {10, 20, 30, 40, 50};

// 1番
for (int i = 0; i < 5; i++) {
    System.out.println(values[i]);
}

// 結果
// ↓
//10
//20
//30
//40
//50
// 2番
try {
    for (int i = 0; i <= 5; i++) {
        System.out.println(values[i]);
    }
} catch (ArrayIndexOutOfBoundsException e) {
    System.out.println(e);
}

// 結果
// ↓
//10
//20
//30
//40
//50
//java.lang.ArrayIndexOutOfBoundsException: Index 5 out of bounds for length 5
// 3番
for (int i = 0; i < values.length; i++) {
    System.out.println(values[i]);
}

// 結果
// ↓
//10
//20
//30
//40
//50
// 4番
for (int i = 0; i < values.length - 1; i++) {
    System.out.println(values[i]);
}

// 結果
// ↓
//10
//20
//30
//40
// 5番
for (int i = 0; i <= values.length - 1; i++) {
    System.out.println(values[i]);
}

// 結果
// ↓
//10
//20
//30
//40
//50

ぱっと見て、というのはなかなか難しいかもしれません。
コードが正しいか確認するために、特に 条件式 の部分を注意深く見たのではないでしょうか。

2番は例外が発生するので 間違い だというのは明確です。
ただ、4番はコーディングミスなのか、あえて最後の要素を省いたのか … というのがコードからは伝わりにくいですね。

あとは、人の好みによって、条件式に "<" を使うか "<=" を使うか別れそうです。

final int[] values = {10, 20, 30, 40, 50};

// どちらも正解です。

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

// 結果
// ↓
//10
//20
//30
//40
//50

for (int i = 0; i <= values.length - 1; i++) {
    System.out.println(values[i]);
}

// 結果
// ↓
//10
//20
//30
//40
//50

そんなあれこれも、拡張for文を使うと条件式もなくなり、すっきりしますね。

final int[] values = {10, 20, 30, 40, 50};

// 拡張for文!
for (int v : values) {
    System.out.println(v);
}

// 結果
// ↓
//10
//20
//30
//40
//50

自分で定義したクラスを拡張for文で使うには

独自のクラスでも、Iterable インタフェースを実装すれば拡張for文で使うことができます。

Sample クラスを例に見てみましょう。
Iterable<String> を実装しているのがポイントです。

public class Sample implements Iterable<String> {
    private static final String[] VALUES = {"aaa", "bbb", "ccc"};
    private int index;

    @Override
    public Iterator<String> iterator() {
        return new Iterator<>() {
            @Override
            public boolean hasNext() {
                return VALUES.length != index;
            }

            @Override
            public String next() {
                final var value = VALUES[index];
                index++;
                return value;
            }
        };
    }
}

それでは、Sample クラスを拡張for文で使ってみましょう。

final var sample = new Sample();
for (final var value : sample) {
    System.out.println(value);
}

// 結果
// ↓
//aaa
//bbb
//ccc

無事に、拡張for文で要素にアクセスできましたね。


forEach メソッド

default void forEach(Consumer<? super T> action)
Iterableの各要素に対して指定されたアクションを、すべての要素が処理されるか、アクションが例外をスローするまで実行します。

Iterable インタフェースには forEach というメソッドがあります。
拡張for文の代わりに ラムダ式メソッド参照 を使って要素にアクセスする、という感じですね。

final var list = List.of("aaa", "bbb", "ccc");

// ラムダ式
list.forEach(value -> {
    System.out.println(value);
});

// 結果
// ↓
//aaa
//bbb
//ccc

// メソッド参照
list.forEach(System.out::println);

// 結果
// ↓
//aaa
//bbb
//ccc

さて、拡張for文と forEach メソッド、どちらを使ったほうがよいのでしょうか…?
それは場合によると思います。

forEach メソッドを使うことのメリットは、そこまで大きくありません。(記述がシンプルになるくらい?)
ラムダ式はチェック例外が扱いにくかったりと、使いにくいところもあります。

forEach メソッドは無理してまでは使わなくてもOK。
使えたら使う、くらいで良いのかな … と個人的には思います。

拡張for文が使えない場面

拡張for文は先頭から1つずつ順番に全ての要素を処理することを目的としています。
例えば、2つおきに処理したい、という場面では使えません。

final int[] values = {10, 20, 30, 40, 50};

for (int i = 0; i < values.length; i += 2) {
    System.out.println(values[i]);
}

// 結果
// ↓
//10
//30
//50

あとは、すべての要素ではなく、例えば3番目まで処理したい、というのも拡張for文ではやりにくいです。

final int[] values = {10, 20, 30, 40, 50};

// 基本for文
for (int i = 0; i < 3; i++) {
    System.out.println(values[i]);
}

// 結果
// ↓
//10
//20
//30

拡張for文で無理やり処理する例です。

final int[] values = {10, 20, 30, 40, 50};

int i = 0;
for (int v : values) {
    i++;
    if (i > 3) {
        break;
    }

    System.out.println(v);
}

// 結果
// ↓
//10
//20
//30

変数が増え、条件式も増え、これでは本末転倒ですね…

そんなときは、ストリーム API を使えばうまくいきます。
こちらはおすすめです。

final int[] values = {10, 20, 30, 40, 50};
Arrays.stream(values).limit(3).forEach(System.out::println);

// 結果
// ↓
//10
//20
//30

TIPS

連番でループ

static IntStream range(int startInclusive, int endExclusive)
startInclusive(含む)からendExclusive(含まない)の範囲でステップ1でインクリメントした値を含む、順序付けされた順次IntStreamを返します。

IntStream.range を使うと簡単に連番のループが可能なのでご紹介します。

IntStream.range(0, 100).forEach(i -> {
    System.out.println(i);
});

// 結果
// ↓
//0
//1
//2
// ... 省略 ...
//98
//99

まとめ

拡張for文が使える場面では、積極的に拡張for文を使っていきましょう。

とはいえ、拡張for文には向かない場面もあります。
そんなときは臨機応変に。(代わりに ストリーム API が使えることもあります)


関連記事

ページの先頭へ