簡體   English   中英

如何檢查 Java 8 Stream 是否為空?

[英]How to check if a Java 8 Stream is empty?

我如何檢查Stream是否為空,如果不是,則拋出異常,作為非終端操作?

基本上,我正在尋找與下面的代碼等效的東西,但沒有具體化中間的 stream。 特別是,在終端操作實際使用 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() ,如果為真,則將IteratorStream 這是低效的,因為流上的所有后續操作都將通過迭代器的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的最后一個片段必須意識到它是最后一個(並且它也不能前進)拋出適當的異常。 所以我沒有在這里添加對拆分的支持。

您必須對 Stream 執行終止操作才能應用任何過濾器。 因此,在您消費它之前,您無法知道它是否為空。

您能做的最好的事情是使用findAny()終端操作終止 Stream,該操作將在找到任何元素時停止,但如果沒有,則必須遍歷所有輸入列表才能找到它。

只有當輸入列表有很多元素並且前幾個元素之一通過過濾器時,這才會對您有所幫助,因為在您知道 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

我能找到的不消耗 stream 或轉換為迭代器的最佳簡單解決方案是:

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

隨時提出改進建議..

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM