如何检查 Java 8 Stream 是否为空?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/26649062/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-11 03:00:05  来源:igfitidea点击:

How to check if a Java 8 Stream is empty?

javajava-8java-stream

提问by Cephalopod

How can I check if a Streamis empty and throw an exception if it's not, as a non-terminal operation?

Stream作为非终端操作,如何检查 a是否为空,如果不是则抛出异常?

Basically, I'm looking for something equivalent to the code below, but without materializing the stream in-between. In particular, the check should not occur before the stream is actually consumed by a terminal operation.

基本上,我正在寻找与下面的代码等效的东西,但没有实现中间的流。特别是,不应在流被终端操作实际使用之前进行检查。

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();
}

采纳答案by Holger

If you can live with limited parallel capablilities, the following solution will work:

如果您可以忍受有限的并行能力,则以下解决方案将起作用:

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);
}

Here is some example code using it:

下面是一些使用它的示例代码:

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);

The problem with (efficient) parallel execution is that supporting splitting of the Spliteratorrequires a thread-safe way to notice whether either of the fragments has seen any value in a thread-safe manner. Then the last of the fragments executing tryAdvancehas to realize that it is the last one (and it also couldn't advance) to throw the appropriate exception. So I didn't add support for splitting here.

(高效)并行执行的问题在于,支持拆分Spliterator需要一种线程安全的方式来注意任一片段是否以线程安全的方式看到任何值。然后执行tryAdvance的最后一个片段必须意识到它是最后一个(并且它也不能前进)抛出适当的异常。所以我没有在这里添加对拆分的支持。

回答by Eran

You must perform a terminal operation on the Stream in order for any of the filters to be applied. Therefore you can't know if it will be empty until you consume it.

您必须对 Stream 执行终止操作才能应用任何过滤器。因此,在您消费它之前,您无法知道它是否为空。

Best you can do is terminate the Stream with a findAny()terminal operation, which will stop when it finds any element, but if there are none, it will have to iterate over all the input list to find that out.

您能做的最好的事情是使用findAny()终端操作终止 Stream,该操作将在找到任何元素时停止,但如果没有,则必须遍历所有输入列表才能找到它。

This would only help you if the input list has many elements, and one of the first few passes the filters, since only a small subset of the list would have to be consumed before you know the Stream is not empty.

只有当输入列表有很多元素并且前几个元素之一通过过滤器时,这才会对您有所帮助,因为在您知道 Stream 不为空之前,只需要消耗列表的一小部分。

Of course you'll still have to create a new Stream in order to produce the output list.

当然,您仍然需要创建一个新的 Stream 才能生成输出列表。

回答by Stuart Marks

The other answers and comments are correct in that to examine the contents of a stream, one must add a terminal operation, thereby "consuming" the stream. However, one can do this and turn the result back into a stream, without buffering up the entire contents of the stream. Here are a couple examples:

其他答案和评论是正确的,因为要检查流的内容,必须添加终端操作,从而“消耗”流。但是,可以这样做并将结果转换回流,而无需缓冲流的全部内容。这里有几个例子:

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());
    }
}

Basically turn the stream into an Iteratorin order to call hasNext()on it, and if true, turn the Iteratorback into a Stream. This is inefficient in that all subsequent operations on the stream will go through the Iterator's hasNext()and next()methods, which also implies that the stream is effectively processed sequentially (even if it's later turned parallel). However, this does allow you to test the stream without buffering up all of its elements.

基本上将流转换为 anIterator以调用hasNext()它,如果为真,则将Iterator返回转换为 a Stream。这是低效的,因为流上的所有后续操作都将通过迭代器hasNext()next()方法,这也意味着流是按顺序有效处理的(即使它后来变为并行)。但是,这确实允许您在不缓冲其所有元素的情况下测试流。

There is probably a way to do this using a Spliteratorinstead of an Iterator. This potentially allows the returned stream to have the same characteristics as the input stream, including running in parallel.

可能有一种方法可以使用 aSpliterator而不是Iterator。这可能允许返回的流具有与输入流相同的特征,包括并行运行。

回答by phoenix7360

Following Stuart's idea, this could be done with a Spliteratorlike this:

按照斯图尔特的想法,这可以通过这样的方式来完成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;
    }
}

I think this works with parallel Streams as the stream.spliterator()operation will terminate the stream, and then rebuild it as required

我认为这适用于并行流,因为stream.spliterator()操作将终止流,然后根据需要重建它

In my use-case I needed a default Streamrather than a default value. that's quite easy to change if this is not what you need

在我的用例中,我需要一个默认值Stream而不是默认值。如果这不是你需要的,那很容易改变

回答by Luis Roberto

I think should be enough to map a boolean

我认为应该足以映射一个布尔值

In code this is:

在代码中,这是:

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

回答by kenglxn

This may be sufficient in many cases

在许多情况下这可能就足够了

stream.findAny().isPresent()