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

时间:2014-10-30 09:18:31

标签: java java-8 java-stream

如何检查Stream是否为空,如果不是,则检查是否为非终端操作?

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

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

7 个答案:

答案 0 :(得分:30)

其他答案和评论是正确的,为了检查流的内容,必须添加终端操作,从而消费&#34;流。但是,可以执行此操作并将结果转换回流,而不会缓冲流的全部内容。以下是几个例子:

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(),如果为真,则将Iterator转回Stream。这是低效的,因为流上的所有后续操作都将通过迭代器的hasNext()next()方法,这也意味着流被有效地顺序处理(即使它是&#39;后来转向平行)。但是,这确实允许您在不缓冲其所有元素的情况下测试流。

使用Spliterator代替Iterator可能有办法实现此目的。这可能允许返回的流具有与输入流相同的特性,包括并行运行。

答案 1 :(得分:29)

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

stream.findAny().isPresent()

答案 2 :(得分:18)

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

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的最后一个片段必须意识到它是最后一个(并且它也无法前进)抛出适当的异常。所以我没有在这里添加对拆分的支持。

答案 3 :(得分:11)

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

你可以做的最好的事情就是使用findAny()终端操作来终止Stream,当它找到任何元素时会停止,但是如果没有,它将必须遍历所有输入列表才能找到它

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

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

答案 4 :(得分:3)

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

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

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

答案 5 :(得分:0)

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

在代码中,这是:

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

答案 6 :(得分:0)

我会简单地使用:

stream.count()>0