Java 8とInfinite Streams

Java 8およびInfinite Streams

1. 概要

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

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

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

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

All Stream operations are divided into intermediate and terminal operationsと結合されて、ストリームパイプラインを形成します。

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

2.1. Intermediate操作

Intermediate操作が実行されないユニット一部のterminal操作が呼び出されます。

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

  • フィルタ()

  • 地図()

  • flatMap()

  • distinct()

  • ソート済み()

  • ピーク()

  • 限定()

  • スキップ()

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

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

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

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

2.2. Terminal操作

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

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

at the moment of processing we need to think carefully if our Stream is properly bounded byは、たとえばlimit()変換であるため、無限ストリームに関しては、端末操作の熱意が重要です。 Terminalの操作は次のとおりです。

  • forEach()

  • forEachOrdered()

  • toArray()

  • reduce()

  • collect()

  • min()

  • max()

  • カウント()

  • anyMatch()

  • allMatch()

  • noneMatch()

  • findFirst()

  • findAny()

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

3. 無限のストリーム

これらの2つの概念(IntermediateTerminalの操作)を理解したので、Streamsの遅延を活用する無限のストリームを作成できます。

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

It is crucial to use a limit() method before executing a collect() methodはターミナル操作です。そうでない場合、プログラムは無期限に実行されます。

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

// when
List 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()端末操作を呼び出しました。 次に、結果のList,に、Stream.の怠惰により、無限シーケンスの最初の10個の要素が含まれます。

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

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

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

Supplier randomUUIDSupplier = UUID::randomUUID;

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

Stream infiniteStreamOfRandomUUID = Stream.generate(randomUUIDSupplier);

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

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

skip()変換を使用して、最初の10個の結果を破棄し、次の10個の要素を取得します。 Supplierインターフェイスの関数を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 integers = Stream
  .iterate(0, i -> i + 1);
integers
  .limit(10)
  .forEach(System.out::println);

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

5. 結論

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

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