1. 概要

この記事では、 java .util.Stream APIを見て、その構成を使用してデータ/要素の無限ストリームを操作する方法を説明します。

要素の無限のシーケンスで作業する可能性は、ストリームが怠惰になるように構築されているという事実に基づいています。

この怠惰は、ストリームで実行できる2つのタイプの操作(中間操作とターミナル操作)を分離することによって実現されます。

2. 中間およびターミナルオペレーション

すべてのストリーム操作は中間操作とターミナル操作に分割され、組み合わされてストリームパイプラインを形成します。

ストリームパイプラインは、ソース(コレクション、配列、ジェネレーター関数、I / Oチャネル、無限シーケンスジェネレーターなど)で構成されます。 その後、0個以上の中間操作と終了操作が続きます。

2.1. 中級操作

中間操作は、ターミナル操作が呼び出されるまで実行されません。

それらは、Stream実行のパイプラインを形成するように構成されています。 中間操作は、次の方法でStreamパイプラインに追加できます。

  • フィルター()
  • 地図()
  • flatMap()
  • 明確()
  • sort()
  • ピーク()
  • limit()
  • スキップ()

すべての中級操作は遅延であるため、処理の結果が実際に必要になるまで実行されません。

基本的に、中間操作は新しいストリームを返します。 中間操作を実行しても、実際には操作は実行されませんが、代わりに、トラバースされると、指定された述語に一致する初期ストリームの要素を含む新しいストリームが作成されます。

そのため、 Stream のトラバーサルは、パイプラインのterminal操作が実行されるまで開始されません。

これは非常に重要なプロパティであり、特に無限ストリームにとって重要です。これにより、Terminal操作が呼び出されたときにのみ実際に呼び出されるストリームを作成できるためです。

2.2. ターミナル操作

Terminal 操作は、ストリームをトラバースして結果または副作用を生成する場合があります。

ターミナル操作が実行された後、ストリームパイプラインは消費されたと見なされ、使用できなくなります。 ほとんどすべての場合、端末操作は熱心であり、データソースのトラバースとパイプラインの処理を完了してから戻ります。

処理の時点で、ストリームが、たとえば limit()変換によって適切に制限されているかどうかを慎重に検討する必要があるため、端末操作の熱意は無限ストリームに関して重要です。 。 Terminal操作は次のとおりです。

  • forEach()
  • forEachOrdered()
  • toArray()
  • 減らす()
  • 収集()
  • min()
  • max()
  • カウント()
  • anyMatch()
  • allMatch()
  • noneMatch()
  • findFirst()
  • findAny()

これらの各操作は、すべての中間操作の実行をトリガーします。

3. 無限のストリーム

中間操作とターミナル操作の2つの概念を理解したので、ストリームの怠惰を活用する無限のストリームを作成できます。

2ずつ増加するゼロからの要素の無限のストリームを作成したいとします。 次に、ターミナル操作を呼び出す前に、そのシーケンスを制限する必要があります。

ターミナル操作であるcollect()メソッドを実行する前に、limit()メソッドを使用することが重要です。そうしないと、プログラムが無期限に実行されます。

// given
Stream<Integer> infiniteStream = Stream.iterate(0, i -> i + 2);

// when
List<Integer> collect = infiniteStream
  .limit(10)
  .collect(Collectors.toList());

// then
assertEquals(collect, Arrays.asList(0, 2, 4, 6, 8, 10, 12, 14, 16, 18));

iterate()メソッドを使用して無限ストリームを作成しました。 次に、 limit()変換と collect()ターミナル操作を呼び出しました。 次に、結果のリストに、ストリームの怠惰のために無限シーケンスの最初の10個の要素が含まれます。

4. カスタムタイプの要素の無限ストリーム

ランダムなUUIDの無限ストリームを作成するとします。

Stream APIを使用してこれを実現するための最初のステップは、これらのランダムな値のSupplierを作成することです。

Supplier<UUID> randomUUIDSupplier = UUID::randomUUID;

サプライヤーを定義するとき、 generate()メソッドを使用して無限のストリームを作成できます。

Stream<UUID> infiniteStreamOfRandomUUID = Stream.generate(randomUUIDSupplier);

次に、そのストリームからいくつかの要素を取得できます。 プログラムを有限時間で終了させたい場合は、 limit()メソッドを使用することを忘れないでください。

List<UUID> randomInts = infiniteStreamOfRandomUUID
  .skip(10)
  .limit(10)
  .collect(Collectors.toList());

skip()変換を使用して、最初の10個の結果を破棄し、次の10個の要素を取得します。 サプライヤーインターフェースの関数をStreamgenerate()メソッドに渡すことにより、任意のカスタムタイプ要素の無限ストリームを作成できます。

6. Do-While –ストリームウェイ

コードに単純なdo..whileループがあるとしましょう。

int i = 0;
while (i < 10) {
    System.out.println(i);
    i++;
}

iカウンターを10回印刷しています。 このような構成は、 Stream APIを使用して簡単に記述できることが期待できます。理想的には、ストリームに doWhile()メソッドがあります。

残念ながら、ストリームにはそのようなメソッドはありません。標準の do-while ループと同様の機能を実現する場合は、 limit()メソッドを使用する必要があります。

Stream<Integer> integers = Stream
  .iterate(0, i -> i + 1);
integers
  .limit(10)
  .forEach(System.out::println);

より少ないコードで命令型whileループのような同じ機能を実現しましたが、 limit()関数の呼び出しは、 doWhile()メソッドをオンにした場合ほど説明的ではありません。 Streamオブジェクト。

5. 結論

この記事では、 StreamAPIを使用して無限のストリームを作成する方法について説明します。 これらをlimit()– などの変換と一緒に使用すると、一部のシナリオの理解と実装が非常に簡単になります。

これらすべての例をサポートするコードは、 GitHubプロジェクトにあります。これはMavenプロジェクトであるため、そのままインポートして実行するのは簡単です。