【问题标题】:How to check if a Java 8 Stream is empty?如何检查 Java 8 Stream 是否为空?
【发布时间】:2014-10-30 09:18:30
【问题描述】:

如何检查 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();
}

【问题讨论】:

  • 你不能一边吃蛋糕一边吃——在这种情况下确实如此。您必须使用 流来确定它是否为空。这就是 Stream 语义(惰性)的重点。
  • 它最终会被消耗,此时应该进行检查
  • 要检查流是否为空,您必须尝试使用​​至少一个元素。到那时,流已经失去了“童贞”,不能从一开始就再次被消费。
  • @MarkoTopolnik 仅仅因为它是惰性的并不意味着它原则上不能缓冲并重新发送被偷看的元素。查看 vavr 流

标签: java java-8 java-stream


【解决方案1】:

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

stream.findAny().isPresent()

【讨论】:

  • 简单明了的解决方案。此代码将消耗流,因此如果我们想在它不为空时进行迭代,我们将不得不创建另一个流。
【解决方案2】:

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

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() 方法,这也意味着流是按顺序有效处理的(即使它后来变成并行处理)。但是,这确实允许您在不缓冲所有元素的情况下测试流。

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

【讨论】:

  • 我不认为有一个可维护的解决方案可以支持高效的并行处理,因为它很难支持拆分,但是拥有estimatedSize 和characteristics 甚至可能会提高单线程性能。碰巧我写了Spliterator 解决方案,而您正在发布Iterator 解决方案……
  • 您可以向流请求一个 Spliterator,调用 tryAdvance(lambda),您的 lambda 会在其中捕获传递给它的任何内容,然后返回一个 Spliterator,它将几乎所有内容委托给底层的 Spliterator,除了它将第一个元素回到第一个块(并修复了estimateSize的结果)。
  • @BrianGoetz 是的,这是我的想法,我只是还没有费心去处理所有这些细节。
  • @Brian Goetz:这就是我所说的“太复杂”的意思。在Stream 之前调用tryAdvance 是否会将Stream 的惰性性质变成“部分惰性”流。这也意味着搜索第一个元素不再是并行操作,因为据我所知,您必须先拆分并同时在拆分部分上执行 tryAdvance 才能执行真正的并行操作。如果唯一的终端操作是findAny 或类似的操作会破坏整个parallel() 请求。
  • 因此,对于完全并行支持,您不能在流调用之前调用tryAdvance,并且必须将每个拆分部分包装到代理中并自行收集所有并发操作的“hasAny”信息并确保如果流为空,则最后一个并发操作会引发所需的异常。很多东西……
【解决方案3】:

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

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

【讨论】:

    【解决方案4】:

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

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

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

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

    【讨论】:

    • 有anyMatch(alwaysTrue()),我觉得最接近hasAny。
    • @MarkoTopolnik 刚刚检查了参考资料——我想到的是 findAny(),尽管 anyMatch() 也可以。
    • anyMatch(alwaysTrue()) 与您的hasAny 的预期语义完美匹配,给您一个boolean 而不是Optional&lt;T&gt;---但我们在这里分裂头发:)
    • 注意alwaysTrue 是番石榴谓词。
    • anyMatch(e -&gt; true) 然后。
    【解决方案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
    

    【讨论】:

    • 如果这就是你所需要的,kenglxn 的答案会更好。
    • 它没用,它重复了 Collection.isEmpty()
    • @Krzysiek 如果您需要过滤集合,这并不是没用的。但是,我同意 Dominykas 的观点,即 kenglxn 的答案更好
    • 因为它也重复了Stream.anyMatch()
    【解决方案6】:

    按照 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;
        }
    }
    

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

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

    【讨论】:

    • 我不知道这是否会显着影响并行流的性能。如果这是一个要求,可能应该测试它
    • 抱歉没有意识到@Holger 也有Spliterator 的解决方案,我想知道两者的比较。
    【解决方案7】:

    我会简单地使用:

    stream.count()>0
    

    【讨论】:

    • 这仅在您不必处理流时才有效。当您想要处理元素时,它不起作用,因为在 count() 之后,流被消耗了。 count 是一个终端操作。
    • 这也不必要地消耗和计数 整个 流,其中“懒惰”(短路).findAny() 只会处理单个项目并完成。
    【解决方案8】:

    我能找到的不消耗流或转换为迭代器的最佳简单解决方案是:

    public Stream<Thing> getFilteredThings() {
        AtomicBoolean found = new AtomicBoolean(false);
        Stream<Thing> stream = getThings().stream()
            .filter(Thing::isFoo)
            .filter(Thing::isBar)
            .forEach(x -> {
                 found.set(true);
                 // do useful things
             })
        ;
        if (!found.get()) {
            throw new RuntimeException("No foo bar things available");
        }
    }
    

    欢迎提出改进建议..

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-01-16
      • 1970-01-01
      • 2021-05-30
      • 2023-03-28
      • 1970-01-01
      • 1970-01-01
      • 2013-06-12
      相关资源
      最近更新 更多