如何检查
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.findAny().isPresent()
其他答案和评论是正确的,因为要检查流的内容,必须添加终端操作,从而“消耗”流。但是,可以执行此操作并将结果转回流中,而无需缓冲流的全部内容。这里有几个例子:
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
来做到这一点。这可能允许返回的流具有与输入流相同的特征,包括并行运行。
如果您可以忍受有限的并行能力,则以下解决方案将起作用:
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
的最后一个片段必须意识到它是最后一个(并且它也无法前进)抛出适当的异常。所以我这里没有添加对拆分的支持。
您必须对流执行终端操作才能应用任何过滤器。因此,直到你吃掉它,你才能知道它是否是空的。
您能做的最好的事情就是使用
findAny()
终端操作终止流,当它找到任何元素时就会停止,但如果没有,则必须迭代所有输入列表才能找到它。
只有当输入列表有很多元素,并且前几个元素之一通过过滤器时,这才会对您有帮助,因为在您知道 Stream 不为空之前,只需消耗列表的一小部分。
当然,您仍然需要创建一个新的 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
按照 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
而不是默认值。如果这不是您需要的,则很容易更改
我会简单地使用:
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");
}
}
随时提出改进建议..
如果你想检查是否为空,并做某事:
.findAny()
.ifPresentOrElse(s -> {},
() -> actionForEmptyList());
如果您需要处理流中的每个项目,除了检查是否为空之外,您还可以使用
peek
:
list.stream()
.filter(Thing::isSomething) // any filters
.peek(this::handleItem) // handling each item in list/stream
.findAny()
.ifPresentOrElse(s -> {},
() -> actionForEmptyList()); // if list is empty, you can fill it