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

Cep*_*pod 83 java java-8 java-stream

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();
}
Run Code Online (Sandbox Code Playgroud)

小智 55

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

stream.findAny().isPresent()
Run Code Online (Sandbox Code Playgroud)

  • 简单明了的解决方案。此代码将消耗该流,因此如果我们想在该流不为空时进行迭代,则必须创建另一个流。 (13认同)
  • 如果您碰巧在需要“count()”之前进行了最后一个“filter()”操作。您可以将序列 `...filter(expr.findAny().isPresent());` 替换为 `...anyMatch(expr)` (3认同)

Stu*_*rks 31

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

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());
    }
}
Run Code Online (Sandbox Code Playgroud)

基本上将流转换为一个Iterator以便调用hasNext()它,如果是,则将其Iterator转换为一个Stream.这是低效的,因为流上的所有后续操作都将通过Iterator hasNext()next()方法,这也意味着流被有效地顺序处理(即使它稍后变为并行).但是,这确实允许您在不缓冲其所有元素的情况下测试流.

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

  • 你可以向流的Spliterator,调用tryAdvance(拉姆达)在您的拉姆达捕捉任何传递给它,然后返回一个Spliterator各位代表几乎所有的底层Spliterator,除了它胶合的第一个元素返回到第一个块(并修复了estimateSize的结果. (2认同)
  • @Brian Goetz:我的意思是"过于复杂".在`Stream`之前调用`tryAdvance`会将`Stream`的懒惰特性变成"部分懒惰"的流.它还意味着搜索第一个元素不再是并行操作,因为你必须首先拆分并同时对拆分部分执行`tryAdvance`以进行真正的并行操作,据我所知.如果唯一的终端操作是`findAny`或类似的会破坏整个`parallel()`请求. (2认同)
  • 因此,对于完全并行支持,您不能在流之前调用`tryAdvance`并且必须将每个拆分部分包装到代理中并自己收集所有并发操作的"hasAny"信息并确保最后一个并发操作抛出所需的流是空的异常.很多东西… (2认同)

Hol*_*ger 21

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

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);
}
Run Code Online (Sandbox Code Playgroud)

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

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);
Run Code Online (Sandbox Code Playgroud)

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


Era*_*ran 12

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

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

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

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

  • `anyMatch(e - > true)`那么. (9认同)
  • 有'anyMatch(alwaysTrue())`,我认为它最接近`hasAny`. (6认同)
  • `anyMatch(alwaysTrue())`完全匹配你的`hasAny`的预期语义,给你一个`boolean`而不是`Optional <T>`---但我们在这里分裂头发:) (3认同)

Lui*_*rto 7

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

在代码中,这是:

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
Run Code Online (Sandbox Code Playgroud)


pho*_*360 5

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

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

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