広告

Java : 拡張for文 (for-eachループ文)

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

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


拡張for文

コード例

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

final String[] strings = {"a", "b", "c"};

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

    String s = strings[i];

    // "a"
    // "b"
    // "c"
    System.out.println(s);
}

// 拡張for文
for (final String s : strings) {
    // "a"
    // "b"
    // "c"
    System.out.println(s);
}

拡張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("a", "b", "c");

for (final String s : list) {
    // "a"
    // "b"
    // "c"
    System.out.println(s);
}

メリット

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

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

for (int i = 0; i < values.length; i++) {
    ...
}

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

条件式(if文switch基本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++) {
    // 10
    // 20
    // 30
    // 40
    // 50
    System.out.println(values[i]);
}

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

// 3番
for (int i = 0; i < values.length; i++) {
    // 10
    // 20
    // 30
    // 40
    // 50
    System.out.println(values[i]);
}

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

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

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

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

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

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

// どちらも正解です。

for (int i = 0; i < values.length; i++) {
    // 10
    // 20
    // 30
    // 40
    // 50
    System.out.println(values[i]);
}

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

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

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

for (final int v : values) {
    // 10
    // 20
    // 30
    // 40
    // 50
    System.out.println(v);
}

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

Iterableインタフェースを実装すれば、拡張for文で使うことができるようになります。
コード例を以下に示します。

public class Sample implements Iterable<String> {
    private static final String[] VALUES = {"a", "b", "c", "d", "e"};
    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;
            }
        };
    }
}
final var sample = new Sample();
for (final var value : sample) {
    // "a"
    // "b"
    // "c"
    // "d"
    // "e"
    System.out.println(value);
}

forEach メソッド

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

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

forEachメソッドを使うと、さらにシンプルに記述することができます。

final var list = List.of("a", "b", "c");

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

// メソッド参照

// "a"
// "b"
// "c"
list.forEach(System.out::println);

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

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

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

拡張for文が使えない場面

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

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

// 基本for文
for (int i = 0; i < values.length; i += 2) {
    // 10
    // 30
    // 50
    System.out.println(values[i]);
}

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

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

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

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

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

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

    // 10
    // 20
    // 30
    System.out.println(v);
}

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

Streamインタフェースを使えばうまくいきます。こちらはおすすめです。

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

//10
//20
//30
Arrays.stream(values).limit(3).forEach(System.out::println);

TIPS

連番でループ

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

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

IntStream.range(0, 100).forEach(i -> {
    // 0
    // 1
    // 2
    // ... 省略
    // 98
    // 99
    System.out.println(i);
});

まとめ

拡張for文が使える場面では、積極的に拡張for文を使っていきましょう。
とはいえ、拡張for文には向かない場面もあります。そのときは臨機応変に。(Streamが使える場面もあります)


関連記事

ページの先頭へ