2014-10-30 13 views
50

Come posso verificare se uno Stream è vuoto e lanciare un'eccezione se non lo è, come operazione non terminale?Come verificare se Java 8 Stream è vuoto?

Fondamentalmente, sto cercando qualcosa di equivalente al codice qui sotto, ma senza materializzare il flusso intermedio. In particolare, il controllo non dovrebbe verificarsi prima che il flusso sia effettivamente consumato da un'operazione terminale.

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

Non puoi avere la tua torta e mangiarla anche - e letteralmente così, in questo contesto. Devi * consumare * il flusso per scoprire se è vuoto. Questo è il punto della semantica di Stream (pigrizia). –

+0

Alla fine verrà consumato, a questo punto il controllo dovrebbe avvenire – Cephalopod

+6

Per verificare che lo stream non sia vuoto devi tentare di consumare almeno un elemento. A quel punto il flusso ha perso la sua "verginità" e non può essere nuovamente consumato dall'inizio. –

risposta

12

Se si può vivere con capablilities parallele limitate, la seguente soluzione funziona:

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

Ecco qualche esempio di codice di usarlo:

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

Il problema con (efficiente) esecuzione parallela è che il supporto della suddivisione di Spliterator richiede un modo sicuro per i thread per notare se uno dei frammenti ha visto qualsiasi valore in un modo sicuro per i thread. Quindi l'ultimo dei frammenti che eseguono tryAdvance deve rendersi conto che è l'ultimo (e non può nemmeno avanzare) a generare l'eccezione appropriata. Quindi non ho aggiunto il supporto per suddividere qui.

10

è necessario eseguire un'operazione di terminale sul flusso in modo che uno dei filtri da applicare. Quindi non puoi sapere se sarà vuoto finché non lo consumi.

Il meglio che puoi fare è terminare il flusso con un'operazione del terminale findAny(), che si interrompe quando trova qualsiasi elemento, ma se non ce ne sono, dovrà scorrere tutte le liste di input per scoprirlo.

Questo sarebbe utile solo se l'elenco di input ha molti elementi e uno dei primi passa i filtri, poiché solo un piccolo sottoinsieme dell'elenco dovrebbe essere consumato prima di sapere che lo Stream non è vuoto.

Ovviamente dovrete ancora creare un nuovo Stream per produrre l'elenco di output.

+2

C'è 'anyMatch (alwaysTrue())', penso che sia il più vicino a 'hasAny'. –

+1

@MarkoTopolnik Ho appena controllato il riferimento - quello che avevo in mente era findAny(), anche se anyMatch() avrebbe funzionato. – Eran

+3

'anyMatch (alwaysTrue())' corrisponde perfettamente alla semantica prevista del tuo 'hasAny', dandoti un' booleano' invece di 'Opzionale ' --- ma stiamo dividendo i capelli qui :) –

23

Le altre risposte e commenti sono corretti in quanto per esaminare il contenuto di un flusso, è necessario aggiungere un'operazione terminale, quindi "consumare" il flusso. Tuttavia, si può fare questo e trasformare il risultato in uno stream, senza bufferizzare l'intero contenuto del flusso. Qui ci sono un paio di esempi:

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

girare Fondamentalmente il flusso in un Iterator al fine di chiamare hasNext() su di esso, e se è vero, ruotare la Iterator di nuovo in un Stream. Ciò è inefficiente in quanto tutte le operazioni successive sullo stream passeranno attraverso i metodi hasNext() e-di Iterator, il che implica anche che il flusso viene elaborato in modo sequenziale (anche se in seguito viene ruotato in parallelo). Tuttavia, questo ti permette di testare il flusso senza buffering su tutti i suoi elementi.

Probabilmente esiste un modo per farlo utilizzando uno Spliterator anziché uno Iterator. Ciò consente potenzialmente al flusso restituito di avere le stesse caratteristiche del flusso di input, inclusa l'esecuzione in parallelo.

+1

Non penso che esista una soluzione manutenibile che supporti l'elaborazione parallela efficiente poiché è difficile supportare la suddivisione, tuttavia avere 'EstimateSize' e' features' potrebbero anche migliorare le prestazioni single-threaded. È appena successo che ho scritto la soluzione 'Spliterator' mentre stavi postando la soluzione' Iterator' ... – Holger

+1

Puoi chiedere al flusso di uno Spliterator, chiamare tryAdvance (lambda) dove il tuo lambda cattura qualcosa passato ad esso, e quindi restituire uno Spliterator che delega quasi tutto allo Spliterator sottostante, tranne che incolla il primo elemento sul primo blocco (e risolve il risultato di estimateSize). –

+1

@BrianGoetz Sì, quello era il mio pensiero, non mi ero ancora preso la briga di passare il lavoro sulle gambe per gestire tutti quei dettagli. –

1

seguente idea di Stuart, questo potrebbe essere fatto con un Spliterator come questo:

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

Penso che questo funziona con flussi paralleli come l'operazione stream.spliterator() terminerà il torrente, e poi ricostruirlo come richiesto

Nel mio caso d'uso avevo bisogno di un valore predefinito Stream anziché di un valore predefinito. questo è abbastanza facile da cambiare se questo non è quello che ti serve

+0

Non riesco a capire se questo potrebbe avere un impatto significativo sulle prestazioni con flussi paralleli. Probabilmente dovrebbe testarlo se questo è un requisito – phoenix7360

+0

Sorry non ha capito che @Holger aveva anche una soluzione con 'Spliterator' Mi chiedo come i due si confrontino. – phoenix7360