1. 概要

Java 8 Stream API は、 Javaコレクションに代わる効率的な代替手段を提供し、結果セットをレンダリングまたは処理します。 ただし、どちらをいつ使用するかを決定することは一般的なジレンマです。

この記事では、StreamCollectionについて説明し、それぞれの用途に適したさまざまなシナリオについて説明します。

2. コレクション対。 ストリーム

Java Collection は、次のようなデータ構造を提供することにより、データを格納および処理するための効率的なメカニズムを提供します。 リスト設定、 と 地図.

ただし、Stream APIは、中間ストレージを必要とせずにデータに対してさまざまな操作を実行するのに役立ちます。 したがって、 Stream は、コレクションや I/Oリソースなどの基盤となるストレージからデータに直接アクセスする場合と同様に機能します。

さらに、コレクションは主にデータへのアクセスとデータを変更する方法を提供することに関係しています。 一方、ストリームはデータの効率的な送信に関係しています。

Javaを使用すると、コレクションからストリームに、またはその逆に簡単に変換できますが、結果セットをレンダリング/処理するための最良のメカニズムを知っておくと便利です。

たとえば、streamおよびparallelStreamメソッドを使用して、CollectionStreamに変換できます。

public Stream<String> userNames() {
    ArrayList<String> userNameSource = new ArrayList<>();
    userNameSource.add("john");
    userNameSource.add("smith");
    userNameSource.add("tom");
    return userNames.stream();
}

同様に、Stream API のcollectメソッドを使用して、ストリームをコレクションに変換できます。

public List<String> userNameList() {
    return userNames().collect(Collectors.toList());
}

ここでは、Collectors.toList()メソッドを使用してストリームをリストに変換しました。 同様に、aストリームをSetに、またはをMapに変換できます。

public static Set<String> userNameSet() {
    return userNames().collect(Collectors.toSet());
}

public static Map<String, String> userNameMap() {
    return userNames().collect(Collectors.toMap(u1 -> u1.toString(), u1 -> u1.toString()));
}

3. ストリームをいつ返すか?

3.1. 高い実体化コスト

Stream APIは、外出先での結果の遅延実行とフィルタリングを提供します。これは、マテリアライゼーションのコストを削減するための最も効果的な方法です。

たとえば、 Java NIOファイルクラスのreadAllLinesメソッドは、JVMがファイルの内容全体をメモリに保持する必要があるファイルのすべての行をレンダリングします。 したがって、この方法では、行のリストを返すために高い実体化コストがかかります。

ただし、 Filesクラスは、Stream を返すlinesメソッドも提供します。このメソッドを使用して、すべての行をレンダリングしたり、limitメソッドを使用して結果セットのサイズをさらに制限したりできます。両方とも怠惰な実行:

Files.lines(path).limit(10).collect(toList());

また、 Stream は、forEachのようなターミナル操作を呼び出すまで中間操作を実行しません。

userNames().filter(i -> i.length() >= 4).forEach(System.out::println);

したがって、 Stream は、時期尚早の具体化に関連するコストを回避します。

3.2. 大きなまたは無限の結果

Stream は、パフォーマンスが向上し、結果が大きくなるか、無限になるように設計されています。 したがって、このようなユースケースにはStreamを使用することをお勧めします。

また、結果が無限の場合、通常、結果セット全体を処理することはありません。 そのため、filterlimitなどのStreamAPIの組み込み機能は、目的の結果セットを処理するのに便利であり、Streamを選択することをお勧めします。

3.3. 柔軟性

Stream は、結果を任意の形式または順序で処理できるようにするために非常に柔軟性があります。

Stream は、一貫した結果セットをコンシューマーに強制したくない場合の明白な選択です。 さらに、 Stream は、消費者に切望されている柔軟性を提供したい場合に最適です。

たとえば、Stream APIで利用可能なさまざまな操作を使用して、結果をフィルタリング/順序付け/制限できます。

public static Stream<String> filterUserNames() {
    return userNames().filter(i -> i.length() >= 4);
}

public static Stream<String> sortUserNames() {
    return userNames().sorted();
}

public static Stream<String> limitUserNames() {
    return userNames().limit(3);
}

3.4. 機能的動作

Streamは機能しています。 さまざまな方法で処理された場合、ソースを変更することはできません。 したがって、不変の結果セットをレンダリングすることをお勧めします。

たとえば、プライマリStreamから受信した一連の結果をfilterlimitにしましょう。

userNames().filter(i -> i.length() >= 4).limit(3).forEach(System.out::println);

ここで、Streamfilterlimitのような操作は、毎回新しい Stream を返し、ソースを変更しません。 userNamesメソッドによって提供されるStream

4. コレクションをいつ返すか?

4.1. 低い実体化コスト

低い実体化コストを伴う結果をレンダリングまたは処理するときに、ストリームよりもコレクションを選択できます。

言い換えれば、Javaは最初にすべての要素を計算することによってコレクションを熱心に構築します。 したがって、結果セットが大きい Collection は、実体化のヒープメモリに大きなプレッシャーをかけます。

したがって、 Collection を検討して、その実体化のためにヒープメモリにあまり圧力をかけない結果セットをレンダリングする必要があります。

4.2. 固定フォーマット

コレクションを使用して、ユーザーに一貫した結果セットを適用できます。 たとえば、TreeSetTreeMapのようなCollectionは、自然に順序付けられた結果を返します。

つまり、 Collection を使用すると、各コンシューマーが同じ結果セットを同じ順序で受信して処理できるようになります。

4.3. 再利用可能な結果

結果がコレクションの形式で返される場合、複数回簡単にトラバースできます。 ただし、ストリームはトラバースされると消費されたと見なされ、再利用されるとIllegalStateExceptionがスローされます

public static void tryStreamTraversal() {
    Stream<String> userNameStream = userNames();
    userNameStream.forEach(System.out::println);
    
    try {
        userNameStream.forEach(System.out::println);
    } catch(IllegalStateException e) {
        System.out.println("stream has already been operated upon or closed");
    }
}

したがって、 Collection を返すことは、コンシューマーが結果を複数回トラバースすることが明らかな場合に適しています。

4.4. 変形

コレクションは、ストリームとは異なり、結果ソースからの要素の追加や削除などの要素の変更を可能にします。 したがって、コレクションを使用して結果セットを返し、コンシューマーによる変更を許可することを検討できます。

たとえば、 add / remove メソッドを使用して、ArrayListを変更できます。

userNameList().add("bob");
userNameList().add("pepper");
userNameList().remove(2);

同様に、putremoveなどのメソッドを使用すると、マップを変更できます。

Map<String, String> userNameMap = userNameMap();
userNameMap.put("bob", "bob");
userNameMap.remove("alfred");

4.5. インメモリ結果

さらに、コレクションの形式でマテリアライズされた結果がすでにメモリに存在する場合は、コレクションを使用することをお勧めします。

5. 結論

この記事では、Streamとを比較しました。 コレクションと、それらに適したさまざまなシナリオを検討しました。

Stream は、遅延初期化、待望の柔軟性、機能的な動作などの利点を備えた、大規模または無限の結果セットをレンダリングするための優れた候補であると結論付けることができます。

ただし、一貫した形式の結果が必要な場合、またはマテリアライゼーションが低い場合は、StreamではなくCollectionを選択する必要があります。

いつものように、ソースコードはGitHubから入手できます。