2015-04-28 16 views
5
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8); 
List<Integer> twoEvenSquares = numbers.stream().filter(n -> { 
    System.out.println("filtering " + n); 
    return n % 2 == 0; 
}).map(n -> { 
    System.out.println("mapping " + n); 
    return n * n; 
}).limit(2).collect(Collectors.toList()); 


for(Integer i : twoEvenSquares) 
{ 
    System.out.println(i); 
} 

quando eseguita la logica sottostante uscita venneJava 8 operazioni streaming in ordine di esecuzione

filtering 1 
filtering 2 
mapping 2 
filtering 3 
filtering 4 
mapping 4 
4 
16 

se il flusso segue il concetto corto circuito (dove usiamo funzionamento corrente limite), l'uscita deve essere come di seguito:

filtering 1 
filtering 2 
filtering 3 
filtering 4 
mapping 2 
mapping 4 
4 
16 

perché dopo il filtraggio 2, dobbiamo trovare ancora un elemento in più per stratificare limite (2), il funzionamento, quindi perché l'uscita non è venuta come ho spiegato?

+7

Perché "deve" essere come il secondo? Perché non dovrebbe (e non lo è). –

risposta

1

Il comportamento che hai notato è quello corretto. Per scoprire se un numero supera l'intera pipeline Stream, devi eseguire quel numero attraverso tutti i passaggi della pipeline.

filtering 1 // 1 doesn't pass the filter 
filtering 2 // 2 passes the filter, moves on to map 
mapping 2 // 2 passes the map and limit steps and is added to output list 
filtering 3 // 3 doesn't pass the filter 
filtering 4 // 4 passes the filter, moves on to map 
mapping 4 // 4 passes the map and limit steps and is added to output list 

ora la pipeline può terminare, poiché abbiamo due numeri che hanno superato la pipeline.

15

Gli stream sono basati su pull. Solo le operazioni di un terminale (come lo collect) causeranno il consumo di elementi.

Concettualmente ciò significa che collect chiederà un elemento dalla limit, limit dal map e map dal filter, e filter dal flusso.

Schematicamente il codice nella sua domanda porta a

collect 
    limit (0) 
    map 
     filter 
     stream (returns 1) 
     /filter (false) 
     filter 
     stream (returns 2) 
     /filter (true) 
    /map (returns 4) 
    /limit (1) 
    limit (1) 
    map 
     filter 
     stream (returns 3) 
     /filter (false) 
     filter 
     stream (returns 4) 
     /filter (true) 
    /map (returns 16) 
    /limit (2) 
    limit (2) 
    /limit (no more items; limit reached) 
/collect 

E questo è conforme alla prima stampa.

+0

Sei a conoscenza di strumenti in grado di prendere codice basato su stream e di tradurlo in output come questo? Ho provato a fare una domanda al riguardo, ma è stato considerato fuori tema. –

+0

@MattPassell No, mi dispiace. –

1

filter e map sono operazioni intermedie. Come afferma la documentazione:

Le operazioni intermedie restituiscono un nuovo flusso. Sono sempre pigri; eseguendo un'operazione intermedia come filter() in realtà non esegue alcun filtro, ma crea invece un nuovo flusso che, quando attraversato, contiene gli elementi del flusso iniziale che corrispondono al predicato dato . Traversal della sorgente della pipeline non inizia fino il terminale funzionamento della pipeline è eseguito.

[...]

Processing flussi pigramente permette efficienze significative; in una pipeline come l'esempio di filtro-mappa-somma sopra, filtraggio, mappatura e sommatoria possono essere fusi in un singolo passaggio sui dati, con uno stato intermedio minimo.

Così, quando si chiama il funzionamento del terminale (cioè collect()), si può pensare a qualcosa di simile (questo è davvero semplificato (che verrà utilizzato il collettore per accumula il contenuto del gasdotto, Streams non sono iterabile, .. .) E non compila ma è solo a visualizzare cose):

public List collectToList() { 
    List list = new ArrayList(); 
    for(Elem e : this) { 
     if(filter.test(e)) { //here you see the filter println 
      e = mapping.apply(e); //here you see the mapping println 
      list.add(e); 
      if(limit >= list.size()) 
       break; 
     } 
    } 
    return list; 
} 
2

Questo è il risultato della pigrizia esecuzione/valutazione delle operazioni flusso intermedie.

La catena di operazioni viene ponderata in ordine inverso passando da collect() a filter(), i valori vengono consumati da ogni passaggio non appena vengono prodotti dal passaggio precedente.

Per descrivere più chiaramente cosa sta succedendo:

  1. L'unica operazione terminale collect() inizia la valutazione della catena.
  2. limit() inizia la valutazione della sua antenato
  3. map() inizia la valutazione della sua antenato
  4. filter() inizia consumando valori dal flusso di fonti
  5. 1 viene valutata, 2 viene valutata e viene prodotto il primo valore
  6. map() utilizza il primo valore restituito dal suo antenato e produce anche un valore
  7. limit() consumare tale valore
  8. collect() raccogliere il primo valore
  9. limit() richiede altri valori dal map() sorgente
  10. map() richiede un altro valore è antenato
  11. filter() riprendere la valutazione per produrre un altro risultato e dopo aver valutato 3 e 4 produrre il nuovo valore 4
  12. map() consuma e produce un nuovo valore
  13. limit() consuma il nuovo valore e lo restituisce
  14. collect() raccoglie l'ultimo valore.

Dal java.util.stream docs:

operazioni flusso sono divisi in intermedi e terminali operazioni, e sono combinati per formare condotte streaming. Una pipeline dello stream è costituita da un'origine (ad esempio una raccolta, una matrice, una funzione generatore o un canale I/O); seguito da zero o più operazioni intermedie come Stream.filter o Stream.map; e un'operazione del terminale come Stream.forEach o Stream.reduce.

Le operazioni intermedie restituiscono un nuovo flusso. Sono sempre pigri; esecuzione di un'operazione intermedia come filtro() non effettivamente eseguire una filtrazione, ma invece crea un nuovo flusso che, quando attraversato, contiene gli elementi del flusso iniziale che corrispondono al proposta predicato.L'attraversamento della sorgente della tubazione non inizia fino al l'operazione del terminale della pipeline viene eseguita.

1

L'API Stream non è intesa a fornire garanzie sull'ordine di esecuzione delle operazioni. Ecco perché dovresti usare le funzioni senza effetti collaterali. Il "cortocircuito" non cambia nulla al riguardo, si tratta solo di non eseguire più operazioni del necessario (e di completarlo in tempo limitato quando possibile, anche per fonti di flusso infinite). E quando guardi i tuoi risultati troverai che tutto funziona bene. Le operazioni eseguite corrispondono a quelle che ti aspettavi e così fa il risultato.

Solo l'ordine non corrisponde e che non è a causa del concetto di ma il vostro presupposto sbagliato circa l'implementazione . Ma se si pensa a come deve apparire un'implementazione che non utilizza uno storage intermedio, si giungerà alla conclusione che deve essere esattamente come osservato. Un Stream elaborerà ogni elemento uno dopo l'altro, filtrandolo, mappandolo e raccogliendolo prima del successivo.