1. 概要

Java8で導入されたforEachループは、プログラマーにコレクションを反復処理するための新しい簡潔で興味深い方法を提供します。

このチュートリアルでは、コレクションで forEach を使用する方法、必要な引数の種類、およびこのループが拡張されたfor-loopとどのように異なるかを説明します。

Java 8の概念をブラッシュアップする必要がある場合は、記事のコレクションが役立ちます。

2. forEachの基本

Javaでは、CollectionインターフェースのスーパーインターフェースとしてIterableがあります。 そして、このインターフェースには、Java8以降の新しいAPIがあります。

void forEach(Consumer<? super T> action)

簡単に言えば、forEachJavadocは、「 Iterable の各要素に対して、すべての要素が処理されるか、アクションが例外。”

したがって、 forEach を使用すると、他の Iterator と同様に、コレクションを反復処理して、各要素に対して特定のアクションを実行できます。

たとえば、StringsCollectionを繰り返して印刷するfor-loopバージョンについて考えてみます。

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

これは、forEachを使用して記述できます。

names.forEach(name -> {
    System.out.println(name);
});

3. forEachメソッドの使用

forEach を使用してコレクションを反復処理し、各要素に対して特定のアクションを実行します。 実行されるアクションは、コンシューマーインターフェイスを実装するクラスに含まれ、引数としてforEachに渡されます。

Consumer インターフェースは、機能インターフェース(単一の抽象メソッドを持つインターフェース)です。 入力を受け入れ、結果を返しません。

定義は次のとおりです。

@FunctionalInterface
public interface Consumer {
    void accept(T t);
}

したがって、たとえば、Stringを単に出力するコンシューマーなどの実装は次のようになります。

Consumer<String> printConsumer = new Consumer<String>() {
    public void accept(String name) {
        System.out.println(name);
    };
};

引数としてforEachに渡すことができます。

names.forEach(printConsumer);

ただし、コンシューマーを介してアクションを作成し、 forEachAPIを使用する方法はこれだけではありません。

forEachメソッドを使用する最も一般的な3つの方法を見てみましょう。

3.1. 匿名コンシューマー実装

匿名クラスを使用してConsumerインターフェイスの実装をインスタンス化し、それを引数としてforEachメソッドに適用できます。

Consumer<String> printConsumer= new Consumer<String>() {
    public void accept(String name) {
        System.out.println(name);
    }
};
names.forEach(printConsumer);

これはうまく機能します。 しかし、例を分析すると、有用な部分は実際には accept()メソッド内のコードであることがわかります。

Lambda式は今では標準であり、これを行うためのより簡単な方法ですが、Consumerインターフェースを実装する方法を知ることは依然として価値があります。

3.2. ラムダ式

Java 8関数インターフェースの主な利点は、Lambda式を使用してそれらをインスタンス化し、かさばる無名クラスの実装を使用しないようにできることです。

Consumer インターフェースは機能的なインターフェースであるため、Lambdaで表現できます。

(argument) -> { //body }

したがって、printConsumerは単純化されています。

name -> System.out.println(name)

そして、それをforEachに渡すことができます。

names.forEach(name -> System.out.println(name));

Java 8でLambda式が導入されて以来、これはおそらくforEachメソッドを使用する最も一般的な方法です。

ラムダには非常に現実的な学習曲線があるため、始めたばかりの場合は、この記事で、新しい言語機能を使用するためのいくつかの優れた方法について説明します。

3.3. メソッドリファレンス

クラスで操作を実行するためのメソッドがすでに存在する通常のLambda構文の代わりに、メソッド参照構文を使用できます。

names.forEach(System.out::println);

4. forEachの操作

4.1. コレクションを反復処理する

タイプコレクションリストセットキューなどの反復可能。 —forEach。を使用する場合と同じ構文です。

したがって、これまで見てきたように、リストの要素を次のように繰り返すことができます。

List<String> names = Arrays.asList("Larry", "Steve", "James");

names.forEach(System.out::println);

そして、セットは似ています:

Set<String> uniqueNames = new HashSet<>(Arrays.asList("Larry", "Steve", "James"));

uniqueNames.forEach(System.out::println);

最後に、コレクションでもあるキューを見てみましょう。

Queue<String> namesQueue = new ArrayDeque<>(Arrays.asList("Larry", "Steve", "James"));

namesQueue.forEach(System.out::println);

4.2. マップのforEachを使用してマップを反復処理する

マップはIterableではありませんが、は、 BiConsumerを受け入れるforEachの独自のバリアントを提供します。

Java 8では、IterableのforEachConsumerの代わりにBiConsumerが導入され、Mapのキーと値の両方でアクションを実行できるようになりました。 同時に。

次のエントリを使用してマップを作成しましょう。

Map<Integer, String> namesMap = new HashMap<>();
namesMap.put(1, "Larry");
namesMap.put(2, "Steve");
namesMap.put(3, "James");

次に、Mapの forEach を使用して、namesMapを繰り返し処理します。

namesMap.forEach((key, value) -> System.out.println(key + " " + value));

ここでわかるように、 BiConsumer を使用して、Mapのエントリを反復処理しました。

(key, value) -> System.out.println(key + " " + value)

4.3. マップを反復するentrySetによる反復

IterableのforEachを使用して、MapEntrySetを反復することもできます。

マップのエントリはEntrySetというセットに格納されているため、forEachを使用してこれを繰り返すことができます。

namesMap.entrySet().forEach(entry -> System.out.println(
  entry.getKey() + " " + entry.getValue()));

5. ForeachとFor-Loop

単純な観点からは、両方のループが同じ機能を提供します。つまり、コレクション内の要素をループします。

それらの主な違いは、それらが異なるイテレータであるということです。 拡張されたfor-loopは外部イテレーターですが、新しいforEachメソッドは内部です。

5.1. 内部イテレータ— forEach

このタイプのイテレーターは、バックグラウンドで反復を管理し、プログラマーがコレクションの要素で実行されることを意図したものをコーディングするだけになります。

代わりに、イテレータは反復を管理し、要素を1つずつ処理するようにします。

内部イテレータの例を見てみましょう。

names.forEach(name -> System.out.println(name));

上記のforEachメソッドでは、提供された引数がラムダ式であることがわかります。 これは、メソッドが何をすべきかを知るだけでよく、反復のすべての作業が内部で処理されることを意味します。

5.2. 外部イテレータ—forループ

外部イテレータは、ループが実行されるwhathowを混合します。

列挙型イテレータおよび拡張 for-loop はすべて外部イテレータです(メソッド iterator() next()を思い出してください) またはhasNext()?)。 これらすべてのイテレータでは、反復の実行方法を指定するのが私たちの仕事です。

このおなじみのループを考えてみましょう。

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

リストを反復処理しているときにhasNext()または next()メソッドを明示的に呼び出すことはありませんが、この反復を機能させる基礎となるコードはこれらのメソッドを使用します。 これは、これらの操作の複雑さがプログラマーから隠されていることを意味しますが、それはまだ存在しています。

コレクション自体が反復を実行する内部イテレータとは異なり、ここでは、コレクションからすべての要素を取り出す外部コードが必要です。

6. 結論

この記事では、forEachループが通常のforループよりも便利であることを示しました。

また、 forEach メソッドがどのように機能するか、およびコレクション内の各要素に対してアクションを実行するために引数としてどのような実装を受け取ることができるかについても説明しました。

最後に、この記事で使用されているすべてのスニペットは、GitHubリポジトリで入手できます。