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


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
您不能也不能吃蛋糕-在这种情况下确实如此。您必须使用流才能发现流是否为空。这就是Stream的语义(懒惰)的关键所在。
Marko Topolnik 2014年

最终将消耗掉它,此时应该进行检查
Cephalopod

11
要检查流是否为空,您必须尝试消耗至少一个元素。此时,流已失去其“原始性”,无法从头开始再次使用。
Marko Topolnik 2014年

Answers:


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。这是低效的,因为对流进行的所有后续操作都将通过Iterator hasNext()next()方法,这也意味着该流将按顺序有效地进行处理(即使稍后将其并行处理)。但是,这确实允许您测试流而不用缓冲其所有元素。

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


1
我认为没有一种可维护的解决方案将支持高效的并行处理,因为它很难支持拆分,但是拥有甚estimatedSizecharacteristics可能会改善单线程性能。碰巧我Spliterator在发布解决方案时写了解决Iterator方案……
Holger 2014年

3
您可以向流请求一个Spliterator,调用tryAdvance(lambda),在那里您的lambda捕获传递给它的任何东西,然后返回一个Spliterator,它将几乎所有内容委派给底层的Spliterator,除了它将第一个元素粘回到第一个块上(并修正估算值的结果)。
Brian Goetz 2014年

1
@BrianGoetz是的,这是我的想法,我只是还没有费心去处理所有这些细节。
斯图尔特·马克

3
@Brian Goetz:那就是我的意思,“太复杂了”。在dos tryAdvance之前调用Stream它将把的惰性性质Stream转变为“部分惰性”流。据我所知,这也意味着搜索第一个元素不再是并行操作,因为您必须先拆分并tryAdvance同时对拆分的部分进行操作才能进行真正的并行操作。如果唯一的终端操作是findAny或类似的操作将破坏整个parallel()请求。
Holger 2014年

2
因此,对于完全并行支持,您必须tryAdvance在流执行之前不要调用它,而必须将每个拆分部分包装到一个代理中,并自行收集所有并发操作的“ hasAny”信息,并确保如果最后一个并发操作抛出所需的异常,流为空。很多东西…
Holger 2014年

18

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

stream.findAny().isPresent()

15

您必须在Stream上执行终端操作,才能应用任何过滤器。因此,直到消耗掉它,您才能知道它是否为空。

最好的办法是使用 findAny()终端操作当找到任何元素时,该操作将停止,但是如果没有任何元素,则必须遍历所有输入列表以找出该元素。

这仅在输入列表包含许多元素并且前几个元素之一通过过滤器的情况下才有帮助,因为在您知道Stream不为空之前,只需消耗列表的一小部分。

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


7
还有anyMatch(alwaysTrue()),我认为这是最接近hasAny
Marko Topolnik 2014年

1
@MarkoTopolnik刚刚检查了参考-我想到的是findAny(),尽管anyMatch()也可以。
伊兰2014年

3
anyMatch(alwaysTrue())完全符合您的预期语义hasAny,给您一个---而boolean不是Optional<T>---但我们在这里
剪头发

1
注意alwaysTrue是番石榴谓词。
让·弗朗索瓦·Savard

10
anyMatch(e -> true)然后。
FBB

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如果您需要过滤集合,这并不是没有用的。但是,我确实同意Dominykas的观点,即kenglxn的答案更好
Hertzu

这是因为它也重复Stream.anyMatch()
Krzysiek

4

遵循Stuart的想法,可以这样来完成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而不是默认值。如果这不是您所需要的,则很容易更改


我不知道这是否会严重影响并行流的性能。如果这是
必需的,

对不起,Spliterator我没有意识到@Holger也有解决方案,我想知道两者之间如何比较。
phoenix7360
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.