web-dev-qa-db-ja.com

ストリームをコピーして、「ストリームがすでに操作されているか、閉じられている」ことを回避します

Java 8ストリームを複製して、2回処理できるようにします。リストとしてcollectして、そこから新しいストリームを取得できます。

// doSomething() returns a stream
List<A> thing = doSomething().collect(toList());
thing.stream()... // do stuff
thing.stream()... // do other stuff

しかし、もっと効率的でエレガントな方法があるべきだと思います。

ストリームをコレクションにしないでストリームをコピーする方法はありますか?

私は実際にEithersのストリームで作業しているので、左投影を1つの方法で処理してから、右投影に移動して別の方法で処理したいです。このようなもの(これまでのところ、toListトリックの使用を余儀なくされています)。

List<Either<Pair<A, Throwable>, A>> results = doSomething().collect(toList());

Stream<Pair<A, Throwable>> failures = results.stream().flatMap(either -> either.left());
failures.forEach(failure -> ... );

Stream<A> successes = results.stream().flatMap(either -> either.right());
successes.forEach(success -> ... );
94
Toby

効率性についてのあなたの仮定は、ちょっと逆向きだと思います。データを一度だけ使用する場合、この巨大な効率の見返りが得られます。データを保存する必要がないため、ストリームは強力な「ループ融合」最適化を提供し、データ全体をパイプラインに効率的に流します。

同じデータを再利用する場合は、定義により、2回(確定的に)生成するか、保存する必要があります。既にコレクションに含まれている場合は、素晴らしいです。それを2回繰り返すのは安価です。

「分岐ストリーム」を使用して設計を実験しました。私たちが見つけたのは、これをサポートするためには実際のコストがかかるということです。珍しいケースを犠牲にして、よくあるケース(1回使用)に負担をかけました。大きな問題は、「2つのパイプラインが同じレートでデータを消費しないとどうなるか」を扱うことでした。とにかくバッファリングに戻ります。これは、明らかにその重みを持たない機能でした。

同じデータを繰り返し操作する場合は、そのデータを保存するか、操作をコンシューマとして構造化し、次の操作を実行します。

stream()...stuff....forEach(e -> { consumerA(e); consumerB(e); });

また、RxJavaライブラリを調べることもできます。その処理モデルは、この種の「ストリームフォーク」により適しているためです。

81
Brian Goetz

Java.util.function.Supplier を使用します。

http://winterbe.com/posts/2014/07/31/Java8-stream-tutorial-examples/ から:

ストリームの再利用

Java 8ストリームは再利用できません。端末操作を呼び出すとすぐに、ストリームが閉じられます。

Stream<String> stream =

Stream.of("d2", "a2", "b1", "b3", "c")

.filter(s -> s.startsWith("a"));

stream.anyMatch(s -> true);    // ok

stream.noneMatch(s -> true);   // exception

同じストリームでanyMatchの後にnoneMatchを呼び出すと、次の例外が発生します。

Java.lang.IllegalStateException: stream has already been operated upon or closed

at 

Java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.Java:229)

at 

Java.util.stream.ReferencePipeline.noneMatch(ReferencePipeline.Java:459)

at com.winterbe.Java8.Streams5.test7(Streams5.Java:38)

at com.winterbe.Java8.Streams5.main(Streams5.Java:28)

この制限を克服するには、実行したいすべての端末操作に対して新しいストリームチェーンを作成する必要があります。ストリームサプライヤを作成して、すべての中間操作が既に設定された新しいストリームを構築できます。

Supplier<Stream<String>> streamSupplier =

    () -> Stream.of("d2", "a2", "b1", "b3", "c")

            .filter(s -> s.startsWith("a"));

streamSupplier.get().anyMatch(s -> true);   // ok

streamSupplier.get().noneMatch(s -> true);  // ok

get()を呼び出すたびに、目的の端末操作を呼び出すために保存する新しいストリームが作成されます。

58
user4975679

jOOQ の統合テストを改善するために作成したオープンソースライブラリ jOOλ で、ストリームのduplicate()メソッドを実装しました。基本的に、あなたは書くことができます:

Tuple2<Seq<A>, Seq<A>> duplicates = Seq.seq(doSomething()).duplicate();

内部的には、1つのストリームからは消費されているが、他のストリームからは消費されていないすべての値を格納するバッファーがあります。これはおそらく、2つのストリームがほぼ同じ速度で消費される場合、およびスレッドセーフの欠如で生活できる場合と同じくらい効率的です。

アルゴリズムの仕組みは次のとおりです。

static <T> Tuple2<Seq<T>, Seq<T>> duplicate(Stream<T> stream) {
    final List<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()));
}

その他のソースコードはこちら

Tuple2はおそらくPair型に似ていますが、SeqStreamにいくつかの拡張が加えられています。

8
Lukas Eder

実行可能ファイルのストリームを作成できます(たとえば)。

results.stream()
    .flatMap(either -> Stream.<Runnable> of(
            () -> failure(either.left()),
            () -> success(either.right())))
    .forEach(Runnable::run);

ここで、failureおよびsuccessは適用する操作です。ただし、これによりかなりの数の一時オブジェクトが作成され、コレクションから開始して2回ストリーミング/反復するよりも効率的ではない場合があります。

7
assylias

サプライヤーを使用して、終了操作ごとにストリームを生成します。

Supplier <Stream<Integer>> streamSupplier=()->list.stream();

そのコレクションのストリームが必要なときはいつでも、streamSupplier.get()を使用して新しいストリームを取得します。

例:

  1. streamSupplier.get().anyMatch(predicate);
  2. streamSupplier.get().allMatch(predicate2);
6
Rams

要素を複数回処理する別の方法は、 Stream.peek(Consumer) を使用することです。

doSomething().stream()
.peek(either -> handleFailure(either.left()))
.foreach(either -> handleSuccess(either.right()));

peek(Consumer)は必要に応じて何度でもチェーンできます。

doSomething().stream()
.peek(element -> handleFoo(element.foo()))
.peek(element -> handleBar(element.bar()))
.peek(element -> handleBaz(element.baz()))
.foreach(element-> handleQux(element.qux()));
3
Martin

cyclops-react 、私が提供しているライブラリには、Streamを複製できる(そしてjOOλTuple of Streamsを返す)静的メソッドがあります。

    Stream<Integer> stream = Stream.of(1,2,3);
    Tuple2<Stream<Integer>,Stream<Integer>> streams =  StreamUtils.duplicate(stream);

コメントを参照してください。既存のストリームで複製を使用するとパフォーマンスが低下します。より高性能な代替手段は、Streamableを使用することです。

Stream、Iterable、またはArrayから構築し、複数回再生できる(遅延)Streamableクラスもあります。

    Streamable<Integer> streamable = Streamable.of(1,2,3);
    streamable.stream().forEach(System.out::println);
    streamable.stream().forEach(System.out::println);

AsStreamable.synchronizedFromStream(stream)-スレッド間で共有できるような方法で、バッキングコレクションを遅延的に設定するStreamableを作成するために使用できます。 Streamable.fromStream(stream)は、同期のオーバーヘッドを引き起こしません。

2
John McClean

同様の問題があり、ストリームのコピーを作成する3つの異なる中間構造、List、配列、Stream.Builderを考えることができました。私は、パフォーマンスの観点から、Listがかなり似ている他の2つよりも約30%遅いことを示唆する小さなベンチマークプログラムを作成しました。

配列に変換することの唯一の欠点は、要素の型がジェネリック型(私の場合はそうです)である場合、注意が必要なことです。したがって、Stream.Builderを使用することを好みます。

私はCollectorを作成する小さな関数を書くことになりました:

private static <T> Collector<T, Stream.Builder<T>, Stream<T>> copyCollector()
{
    return Collector.of(Stream::builder, Stream.Builder::add, (b1, b2) -> {
        b2.build().forEach(b1);
        return b1;
    }, Stream.Builder::build);
}

その後、str.collect(copyCollector())を実行することで、任意のストリームstrのコピーを作成できます。これは、ストリームの慣用的な使用法に非常によく従っています。

0
Jeremy Hicks

この特定の問題には、パーティション分割も使用できます。何かのようなもの

     // Partition Eighters into left and right
     List<Either<Pair<A, Throwable>, A>> results = doSomething();
     Map<Boolean, Object> passingFailing = results.collect(Collectors.partitioningBy(s -> s.isLeft()));
     passingFailing.get(true) <- here will be all passing (left values)
     passingFailing.get(false) <- here will be all failing (right values)
0
Lubomir Varga

ストリームの読み取りまたは反復時にStream Builderを使用できます。以下はStream Builderのドキュメントです。

https://docs.Oracle.com/javase/8/docs/api/Java/util/stream/Stream.Builder.html

ユースケース

従業員ストリームがあり、このストリームを使用して従業員データをExcelファイルに書き込み、従業員コレクション/テーブルを更新する必要があるとします[これはStream Builderの使用を示すための単なる使用例です]:

Stream.Builder<Employee> builder = Stream.builder();

employee.forEach( emp -> {
   //store employee data to Excel file 
   // and use the same object to build the stream.
   builder.add(emp);
});

//Now this stream can be used to update the employee collection
Stream<Employee> newStream = builder.build();
0
Lokesh Singal