2014-10-30 54 views
50

如何檢查Stream是否爲空,如果不是非終端操作,則會拋出異常?如何檢查Java 8 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(); 
} 
+14

你不能擁有你的蛋糕,也不能吃它 - 而且在這種情況下也是如此。你必須*消費*流來查明它是否爲空。這就是Stream的語義(懶惰)。 – 2014-10-30 09:21:11

+0

它將被最終消耗,此時檢查應該發生 – Cephalopod 2014-10-30 09:22:32

+6

要檢查流是否爲空,您必須嘗試使用​​至少一個元素。那時溪流已經失去了「童貞」,從一開始就不能再被消耗掉。 – 2014-10-30 09:27:09

回答

12

如果你可以用有限的平行capablilities直播,以下解決方案將工作:

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的最後一個片段必須認識到它是最後一個(也不能提前)拋出適當的異常。所以我沒有在這裏添加分裂的支持。

10

您必須在流上執行終端操作才能應用任何過濾器。因此,在使用之前,您無法知道它是否爲空。

你可以做的最好的事情是用終端操作終止流,當終端操作發現任何元素時它將停止,但如果沒有,它將不得不迭代所有輸入列表來查找。

如果輸入列表中有許多元素,並且前幾次傳遞過濾器之一,這隻會幫助您,因爲在知道流不是空的之前,只需要使用該列表的一小部分。

當然,您仍然需要創建一個新的Stream來生成輸出列表。

+2

有'anyMatch(alwaysTrue())',我認爲這是最接近'hasAny'。 – 2014-10-30 09:30:08

+1

@MarkoTopolnik剛剛檢查過參考文獻 - 我想到的是findAny(),儘管anyMatch()也可以工作。 – Eran 2014-10-30 11:03:54

+3

'anyMatch(alwaysTrue())'完全匹配你的'hasAny'的預期語義,給你一個'boolean'而不是'Optional '---但是我們在這裏拆分頭髮:) – 2014-10-30 11:19:43

23

其他答案和評論是正確的,因爲要檢查一個流的內容,必須添加一個終端操作,從而「消耗」流。但是,可以執行此操作並將結果重新轉換爲流,而不緩衝流的全部內容。這裏有幾個例子:

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來執行此操作。這可能會使返回的流具有與輸入流相同的特性,包括並行運行。

+1

我不認爲有一個可維護的解決方案可以支持高效的並行處理,因爲它很難支持分割,但有'estimatedSize'和'characteristics'甚至可以提高單線程性能。剛發佈Iterator解決方案時,我寫了'Spliterator'解決方案...... – Holger 2014-10-30 17:42:42

+1

您可以問Spliterator的流,調用tryAdvance(lambda),其中lambda捕獲傳遞給它的任何東西,然後返回Spliterator它將幾乎所有的東西委託給底層的Spliterator,除了它將第一個元素粘貼回第一個塊(並修正了估計尺寸的結果)。 – 2014-10-30 20:55:04

+1

@BrianGoetz是的,這是我的想法,我只是還沒有費心去完成處理所有這些細節的大量工作。 – 2014-10-30 21:19:08

1

繼斯圖爾特的想法,這可能與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而不是默認值。這很容易改變,如果這不是你所需要的

+0

我不明白這是否會顯着影響並行流的性能。應該測試它,如果這是一個要求 – phoenix7360 2017-07-17 10:11:12

+0

對不起沒有意識到@Holger也有'Spliterator'的解決方案我想知道這兩個比較。 – phoenix7360 2017-07-17 10:14:31