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つの概念(IntermediateとTerminalの操作)を理解したので、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インターフェイスの関数をStreamのgenerate()メソッドに渡すことにより、任意のカスタムタイプ要素の無限ストリームを作成できます。
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プロジェクトであるため、そのままインポートして実行するのは簡単です。