如何检查 Java8流是否为空?

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

必须对 Stream 执行终端操作才能应用任何筛选器。因此,你不能知道它是否将是空的,直到你消耗它。

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

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

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

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

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

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

如果你能忍受有限的并行能力,下面的解决方案将会奏效:

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

按照斯图尔特的想法,这可以通过像这样的 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而不是一个默认值。如果这不是你需要的,那就很容易改变

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

在代码中,这是:

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

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

stream.findAny().isPresent()

我会简单地使用:

stream.count()>0

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

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

请随意提出改进建议。