Java 8ストリームが空かどうかを確認する方法は?


95

Stream非ターミナル操作として、a が空かどうかを確認し、空でない場合は例外をスローするにはどうすればよいですか?

基本的に、私は以下のコードと同等のものを探していますが、その間にストリームを具体化していません。特に、ストリームが端末操作によって実際に消費される前にチェックが行われるべきではありません。

public Stream<Thing> getFilteredThings() {
    Stream<Thing> stream = getThings().stream()
                .filter(Thing::isFoo)
                .filter(Thing::isBar);
    return nonEmptyStream(stream, () -> {
        throw new RuntimeException("No foo bar things available")   
    });
}

private static <T> Stream<T> nonEmptyStream(Stream<T> stream, Supplier<T> defaultValue) {
    List<T> list = stream.collect(Collectors.toList());
    if (list.isEmpty()) list.add(defaultValue.get());
    return list.stream();
}

23
あなたはあなたのケーキを持ってそれを食べることもできません-そしてこの文脈では、文字通りそうです。ストリームが空かどうかを確認するには、ストリームを使用する必要があります。それがストリームのセマンティクス(怠惰)のポイントです。
Marko Topolnik 2014年

最終的には消費されますが、この時点でチェックが行われるはずです
頭足類

11
ストリームが空でないことを確認するには、少なくとも1つの要素を消費する必要があります。その時点で、ストリームは「処女」を失い、最初から再び消費することはできません。
Marko Topolnik 2014年

回答:


24

限られた並列能力で生活できる場合は、次の解決策が機能します。

private static <T> Stream<T> nonEmptyStream(
    Stream<T> stream, Supplier<RuntimeException> e) {

    Spliterator<T> it=stream.spliterator();
    return StreamSupport.stream(new Spliterator<T>() {
        boolean seen;
        public boolean tryAdvance(Consumer<? super T> action) {
            boolean r=it.tryAdvance(action);
            if(!seen && !r) throw e.get();
            seen=true;
            return r;
        }
        public Spliterator<T> trySplit() { return null; }
        public long estimateSize() { return it.estimateSize(); }
        public int characteristics() { return it.characteristics(); }
    }, false);
}

これを使用したコードの例をいくつか示します。

List<String> l=Arrays.asList("hello", "world");
nonEmptyStream(l.stream(), ()->new RuntimeException("No strings available"))
  .forEach(System.out::println);
nonEmptyStream(l.stream().filter(s->s.startsWith("x")),
               ()->new RuntimeException("No strings available"))
  .forEach(System.out::println);

(効率的な)並列実行の問題は、の分割を​​サポートするには、Spliteratorいずれかのフラグメントがスレッドセーフな方法で値を確認したかどうかを通知するスレッドセーフな方法が必要であることです。次に、実行中の最後のフラグメントtryAdvanceは、適切な例外をスローするために、それが最後のフラグメントであることを認識しなければなりません(また、進めることもできませんでした)。したがって、ここでは分割のサポートを追加しませんでした。


33

他の回答とコメントは、ストリームの内容を調べるために端末操作を追加し、それによってストリームを「消費」する必要があるという点で正しいです。ただし、これを行うと、ストリームのコンテンツ全体をバッファリングせずに、結果をストリームに戻すことができます。次に例をいくつか示します。

static <T> Stream<T> throwIfEmpty(Stream<T> stream) {
    Iterator<T> iterator = stream.iterator();
    if (iterator.hasNext()) {
        return StreamSupport.stream(Spliterators.spliteratorUnknownSize(iterator, 0), false);
    } else {
        throw new NoSuchElementException("empty stream");
    }
}

static <T> Stream<T> defaultIfEmpty(Stream<T> stream, Supplier<T> supplier) {
    Iterator<T> iterator = stream.iterator();
    if (iterator.hasNext()) {
        return StreamSupport.stream(Spliterators.spliteratorUnknownSize(iterator, 0), false);
    } else {
        return Stream.of(supplier.get());
    }
}

基本的に、それIteratorを呼び出すためにストリームをに変換し、hasNext()trueの場合はにIterator戻しStreamます。これは、ストリームに対する後続のすべての操作がイテレータhasNext()next()メソッドを通過するため非効率的です。これは、ストリームが効率的に順次処理されることを意味します(後で並列化された場合でも)。ただし、これにより、すべての要素をバッファリングせずにストリームをテストできます。

おそらくのSpliterator代わりにを使用してこれを行う方法があるでしょうIterator。これにより、並列実行など、返されるストリームが入力ストリームと同じ特性を持つ可能性があります。


1
私はそこにそれが分割をサポートするのは難しいと効率的な並列処理をサポートする保守性ソリューションは、あるしかし持っていることはないと思うestimatedSizeし、characteristicsでも、シングルスレッドパフォーマンスが向上する可能性があります。Spliteratorあなたが解決策を投稿しているときに私が解決策を書いたのはたまたまIterator
Holger

3
ストリームにスプリッターを要求し、渡されたものをラムダがキャプチャする場所でtryAdvance(lambda)を呼び出し、最初の要素を最初のチャンクに接着することを除いて、基本的なスプリッターにほとんどすべてを委譲するスプリッターを返します(そして、estimateSizeの結果を修正します)。
Brian Goetz 2014年

1
@BrianGoetzはい、それは私の考えでした、私はまだそれらすべての詳細を処理するレッグワークを通過することをまだ気にしていません。
Stuart Marks

3
@ブライアン・ゲッツ:「複雑すぎる」とはそういうことです。のtryAdvance前に呼び出すStreamと、のレイジーな性質がStream「部分的にレイジーな」ストリームに変わります。またtryAdvance、私が理解している限り、最初の要素を検索することは、最初に分割し、分割された部分を同時に実行して実際の並列操作を行う必要があるため、もはや並列操作ではないことも意味します。唯一の端末操作がfindAny同様の場合、parallel()リクエスト全体が破棄されます。
Holger

2
したがって、完全な並列サポートではtryAdvance、ストリームが実行する前に呼び出してはならず、すべての分割部分をプロキシにラップし、すべての同時操作の「hasAny」情報を自分で収集し、最後の同時操作が必要な例外をスローすることを確認する必要があります。ストリームは空でした。多くのもの…
ホルガー

18

多くの場合これで十分かもしれません

stream.findAny().isPresent()

15

フィルタを適用するには、ストリームでターミナル操作を実行する必要があります。したがって、それが消費されるまで、それが空になるかどうかを知ることはできません。

最善の方法は、ストリームをfindAny()ターミナル操作で終了することです。これは、要素が見つかると停止しますが、要素がない場合は、すべての入力リストを反復処理してそれを見つける必要があります。

これは、入力リストに多くの要素があり、最初のいくつかの1つがフィルターを通過する場合にのみ役立ちます。ストリームが空でないことがわかる前に、リストの小さなサブセットのみを消費する必要があるためです。

もちろん、出力リストを作成するために新しいストリームを作成する必要があります。


7
ありanyMatch(alwaysTrue())ますが、に最も近いと思いますhasAny
Marko Topolnik 2014年

1
@MarkoTopolnik参照を確認したところ、私が考えていたのはfindAny()でしたが、anyMatch()も機能します。
エラン2014年

3
anyMatch(alwaysTrue())の意図するセマンティクスに完全に一致しhasAnyboolean代わりにを提供しますOptional<T>---ただし、ここではヘアを分割しています:)
Marko Topolnik 2014年

1
alwaysTrueはグアバ述語です。
ジャン=フランソワ・Savard

10
anyMatch(e -> true)その後。
FBB 2017年

5

ブール値をマッピングするには十分だと思います

コードではこれは:

boolean isEmpty = anyCollection.stream()
    .filter(p -> someFilter(p)) // Add my filter
    .map(p -> Boolean.TRUE) // For each element after filter, map to a TRUE
    .findAny() // Get any TRUE
    .orElse(Boolean.FALSE); // If there is no match return false

1
これで十分な場合は、kenglxnの方が適切です。
Dominykas Mostauskis

役に立たない、それはCollection.isEmpty()を複製します
Krzysiek

@Krzysiekこれは、コレクションをフィルタリングする必要がある場合には役に立ちません。しかし、私はkenglxnの答えが優れているという点でDominykasに同意します
Hertzu

それも重複しているためですStream.anyMatch()
Krzysiek

4

スチュアートのアイデアに従って、これは次のようにして行うことができますSpliterator

static <T> Stream<T> defaultIfEmpty(Stream<T> stream, Stream<T> defaultStream) {
    final Spliterator<T> spliterator = stream.spliterator();
    final AtomicReference<T> reference = new AtomicReference<>();
    if (spliterator.tryAdvance(reference::set)) {
        return Stream.concat(Stream.of(reference.get()), StreamSupport.stream(spliterator, stream.isParallel()));
    } else {
        return defaultStream;
    }
}

stream.spliterator()操作はストリームを終了し、必要に応じて再構築するため、これは並列ストリームで機能すると思います

私のユースケースStreamでは、デフォルト値ではなくデフォルトが必要でした。これが必要なものでない場合は、簡単に変更できます


これが並列ストリームのパフォーマンスに大きな影響を与えるかどうかはわかりません。これが要件である場合はおそらくテストする必要があります
phoenix7360

申し訳ありませんが、@ Holgerにも解決策があることを理解していませんでしたSpliterator
phoenix7360 2017
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.