ストリームに対して一連の操作を実行し、結果のストリームを他の操作で2つの異なる方法で処理したい場合があります。
共通の初期操作を2回指定することなくこれを実行できますか?
たとえば、次のようなdup()
メソッドが存在することを期待しています。
Stream [] desired_streams = IntStream.range(1, 100).filter(n -> n % 2 == 0).dup();
Stream stream14 = desired_streams[0].filter(n -> n % 7 == 0); // multiples of 14
Stream stream10 = desired_streams[1].filter(n -> n % 5 == 0); // multiples of 10
一般的には不可能です。
入力ストリームまたは入力反復子を複製する場合、2つのオプションがあります。
List<>
_と言うストリームを2つのストリーム_s1
_および_s2
_に複製するとします。 _n1
_および_s1
_要素内の_n2
_要素に_s2
_の高度な要素がある場合、ペースを保つために_|n2 - n1|
_要素をメモリに保持する必要があります。ストリームが無限の場合、必要なストレージの上限はないかもしれません。
Pythonの tee()
を見て、必要なものを確認してください。
このitertoolには、大量の補助ストレージが必要になる場合があります(一時データをどれだけ保存する必要があるかによって異なります)。一般に、あるイテレーターが別のイテレーターが開始する前にほとんどまたはすべてのデータを使用する場合、
list()
の代わりにtee()
を使用する方が高速です。
このオプションが機能するためには、おそらくストリームの内部の仕組みにアクセスする必要があります。言い換えると、ジェネレーター-要素を作成する部分-は、最初の場所でコピーをサポートする必要があります。 [OP:これを参照してください great answer 、質問の例でこれを行う方法の例として]
「外の世界」全体の状態をコピーする必要があるため、ユーザーからの入力では機能しません。 JavaのStream
はコピーをサポートしていません。ファイル、ネットワーク、キーボード、センサー、ランダム性などを扱うために可能な限り一般的になるように設計されているためです。[OP:温度を読み取るストリームオンデマンドのセンサー。測定値のコピーを保存せずに複製することはできません]
これはJavaの場合だけではありません。これは一般的なルールです。この理由から、C++の _std::istream
_ は移動セマンティクスのみをサポートし、コピーセマンティクス(「コピーコンストラクター(削除済み)」)をサポートしていることがわかります(その他)。
この方法でストリームを複製することはできません。ただし、共通部分をメソッドまたはラムダ式に移動することにより、コードの重複を回避できます。
Supplier<IntStream> supplier = () ->
IntStream.range(1, 100).filter(n -> n % 2 == 0);
supplier.get().filter(...);
supplier.get().filter(...);
1つの複製で消費した要素をバッファリングしているが、まだ他の要素ではバッファリングしていない場合は可能です。
jOOQ の統合テストを改善するために作成したオープンソースライブラリである jOOλ のストリームにduplicate()
メソッドを実装しました。基本的に、あなたは書くことができます:
Tuple2<Seq<Integer>, Seq<Integer>> desired_streams = Seq.seq(
IntStream.range(1, 100).filter(n -> n % 2 == 0).boxed()
).duplicate();
(注:IntSeq
をまだ実装していないため、現在ストリームをボックス化する必要があります)
内部的には、1つのストリームからは消費されているが他のストリームからは消費されていないすべての値を格納するLinkedList
バッファーがあります。これはおそらく、2つのストリームがほぼ同じ速度で消費される場合と同じくらい効率的です。
アルゴリズムの仕組みは次のとおりです。
static <T> Tuple2<Seq<T>, Seq<T>> duplicate(Stream<T> stream) {
final LinkedList<T> gap = new LinkedList<>();
final Iterator<T> it = stream.iterator();
@SuppressWarnings("unchecked")
final Iterator<T>[] ahead = new Iterator[] { null };
class Duplicate implements Iterator<T> {
@Override
public boolean hasNext() {
if (ahead[0] == null || ahead[0] == this)
return it.hasNext();
return !gap.isEmpty();
}
@Override
public T next() {
if (ahead[0] == null)
ahead[0] = this;
if (ahead[0] == this) {
T value = it.next();
gap.offer(value);
return value;
}
return gap.poll();
}
}
return Tuple(seq(new Duplicate()), seq(new Duplicate()));
}
実際、 jOOλ を使用すると、次のように完全なワンライナーを記述できます。
Tuple2<Seq<Integer>, Seq<Integer>> desired_streams = Seq.seq(
IntStream.range(1, 100).filter(n -> n % 2 == 0).boxed()
).duplicate()
.map1(s -> s.filter(n -> n % 7 == 0))
.map2(s -> s.filter(n -> n % 5 == 0));
// This will yield 14, 28, 42, 56...
desired_streams.v1.forEach(System.out::println)
// This will yield 10, 20, 30, 40...
desired_streams.v2.forEach(System.out::println);
ストリーム生成を、このストリームを返す別のメソッド/関数に移動して、2回呼び出すこともできます。
どちらか、
これには、実行していることを明示するという利点があり、無限ストリームでも機能します。
あなたの例では:
final int[] arr = IntStream.range(1, 100).filter(n -> n % 2 == 0).toArray();
それから
final IntStream s = IntStream.of(arr);
更新:これしない動作します。元の回答のテキストの後の以下の説明を参照してください。
なんてばかげている。私がする必要があるのは次のとおりです。
Stream desired_stream = IntStream.range(1, 100).filter(n -> n % 2 == 0);
Stream stream14 = desired_stream.filter(n -> n % 7 == 0); // multiples of 14
Stream stream10 = desired_stream.filter(n -> n % 5 == 0); // multiples of 10
これが機能しない理由の説明:
コードを作成して両方のストリームを収集しようとすると、最初のストリームは問題なく収集されますが、2番目のストリームをストリーミングしようとすると例外Java.lang.IllegalStateException: stream has already been operated upon or closed
がスローされます。
詳述すると、ストリームはステートフルオブジェクトです(これは、リセットまたは巻き戻しできません)。これらはイテレータと考えることができます。イテレータはポインタのようなものです。したがって、stream14
とstream10
は、同じポインターへの参照と考えることができます。最初のストリームを最後まで消費すると、ポインターは「終わりを過ぎて」移動します。 2番目のストリームを消費しようとすることは、すでに「終わりを過ぎた」ポインターにアクセスしようとするようなもので、これは当然のこととして違法な操作です。
受け入れられた答えが示すように、ストリームを作成するコードは2回実行する必要がありますが、Supplier
ラムダまたは同様の構造に分割できます。
完全なテストコード:Foo.Java
、javac Foo.Java
、Java Foo
の順に保存
import Java.util.stream.IntStream;
public class Foo {
public static void main (String [] args) {
IntStream s = IntStream.range(0, 100).filter(n -> n % 2 == 0);
IntStream s1 = s.filter(n -> n % 5 == 0);
s1.forEach(n -> System.out.println(n));
IntStream s2 = s.filter(n -> n % 7 == 0);
s2.forEach(n -> System.out.println(n));
}
}
出力:
$ javac Foo.Java
$ Java Foo
0
10
20
30
40
50
60
70
80
90
Exception in thread "main" Java.lang.IllegalStateException: stream has already been operated upon or closed
at Java.util.stream.AbstractPipeline.<init>(AbstractPipeline.Java:203)
at Java.util.stream.IntPipeline.<init>(IntPipeline.Java:91)
at Java.util.stream.IntPipeline$StatelessOp.<init>(IntPipeline.Java:592)
at Java.util.stream.IntPipeline$9.<init>(IntPipeline.Java:332)
at Java.util.stream.IntPipeline.filter(IntPipeline.Java:331)
at Foo.main(Foo.Java:8)
非無限ストリームの場合、ソースにアクセスできる場合は、次のように簡単です:
@Test
public void testName() throws Exception {
List<Integer> integers = Arrays.asList(1, 2, 4, 5, 6, 7, 8, 9, 10);
Stream<Integer> stream1 = integers.stream();
Stream<Integer> stream2 = integers.stream();
stream1.forEach(System.out::println);
stream2.forEach(System.out::println);
}
プリント
1 2 4 5 6 7 8 9 10
1 2 4 5 6 7 8 9 10
あなたの場合:
Stream originalStream = IntStream.range(1, 100).filter(n -> n % 2 == 0)
List<Integer> listOf = originalStream.collect(Collectors.toList())
Stream stream14 = listOf.stream().filter(n -> n % 7 == 0);
Stream stream10 = listOf.stream().filter(n -> n % 5 == 0);
パフォーマンスなどについては、他の誰かの答えを読んでください;)