2015-11-16 25 views
6

Mi chiedo se posso aggiungere un'operazione a uno stream, in base a una sorta di condizione impostata all'esterno dello stream. Ad esempio, voglio aggiungere un'operazione di limite allo stream se la mia variabile limit non è uguale a -1.Aggiungere condizionatamente un'operazione a un flusso Java 8

Il mio codice attualmente sembra questo, ma devo ancora vedere altri esempi di corsi d'acqua utilizzati in questo modo, in cui un oggetto Stream viene riassegnato al risultato di un'operazione intermedia applicato su se stessa:

// Do some stream stuff 
stream = stream.filter(e -> e.getTimestamp() < max); 

// Limit the stream 
if (limit != -1) { 
    stream = stream.limit(limit); 
} 

// Collect stream to list 
stream.collect(Collectors.toList()); 

Come indicato in questo stackoverflow post, il filtro non viene effettivamente applicato fino a quando non viene chiamata un'operazione terminale. Poiché sto riassegnando il valore del flusso prima che venga chiamata un'operazione terminale, il codice precedente è ancora un modo corretto per utilizzare i flussi Java 8?

+2

Sembra che tu non stia catturando l'output di 'stream.filter()'. – whaleberg

+1

Se vuoi scriverlo su una sola riga, potresti scrivere '.limit (limit! = -1? Limit: Long.MAX_VALUE)' ma non lo farei. – zapl

+0

@whaleberg, @WillShackleford, @Peter: errore mio, ho dimenticato di assegnare il valore del flusso filtrato a 'stream' nel mio codice di esempio originale. Immagino di aver capito i flussi in modo errato. Ho letto che tutti i flussi dovevano essere seguiti da un'operazione terminale per poter essere eseguiti, quindi ho pensato che fosse scorretto memorizzare il flusso risultante nella stessa variabile 'stream'. Aggiornerò la mia domanda originale – Lani

risposta

10

Non vi è alcuna differenza semantica tra una serie concatenata di invocazioni e una serie di invocazioni che memorizzano i valori di ritorno intermedi. Pertanto, i seguenti frammenti di codice sono equivalenti:

a = object.foo(); 
b = a.bar(); 
c = b.baz(); 

e

c = object.foo().bar().baz(); 

In ogni caso, ogni metodo viene richiamato sul risultato della chiamata precedente. Ma nel secondo caso, i risultati intermedi non vengono memorizzati ma persi al prossimo richiamo. Nel caso dell'API stream, i risultati intermedi non devono essere da utilizzare dopo aver richiamato il metodo successivo su di esso, pertanto il concatenamento è il modo naturale di utilizzare il flusso poiché garantisce intrinsecamente di non richiamare più di un metodo su un riferimento restituito.

Tuttavia, non è sbagliato memorizzare il riferimento a un flusso finché si obbedisce al contratto di non utilizzare un riferimento restituito più di una volta. Usandolo come nella tua domanda, cioè sovrascrivendo la variabile con il risultato della successiva chiamata, assicurati anche di non invocare più di un metodo su un riferimento restituito, quindi, è un uso corretto.Naturalmente, questo funziona solo con risultati intermedi dello stesso tipo, quindi quando si utilizza map o flatMap, ottenendo un flusso di un tipo di riferimento diverso, non è possibile sovrascrivere la variabile locale. Quindi bisogna stare attenti a non riutilizzare la vecchia variabile locale, ma, come detto, fino a quando non la si utilizza dopo l'invocazione successiva, non c'è nulla di sbagliato nella memoria intermedia.

A volte, è avere per memorizzarlo, ad es.

try(Stream<String> stream = Files.lines(Paths.get("myFile.txt"))) { 
    stream.filter(s -> !s.isEmpty()).forEach(System.out::println); 
} 

Si noti che il codice è equivalente alle seguenti alternative:

try(Stream<String> stream = Files.lines(Paths.get("myFile.txt")).filter(s->!s.isEmpty())) { 
    stream.forEach(System.out::println); 
} 

e

try(Stream<String> srcStream = Files.lines(Paths.get("myFile.txt"))) { 
    Stream<String> tmp = srcStream.filter(s -> !s.isEmpty()); 
    // must not be use variable srcStream here: 
    tmp.forEach(System.out::println); 
} 

Sono equivalenti perché forEach viene sempre invocato sul risultato di filter che è sempre invocato sul risultato di Files.lines e non importa su quale risultato viene richiamata l'operazione finale close() d come chiusura interessa l'intera condotta del flusso.


Per dirla in una frase, il modo in cui lo si utilizza, è corretto.


Ho anche preferiscono a farlo in quel modo, come non concatenamento un'operazione limit quando non si desidera applicare un limite è il modo più pulito di espressione vostro intento. E 'anche interessante notare che le alternative suggerite possono lavorare in molti casi, ma sono non semanticamente equivalenti:

.limit(condition? aLimit: Long.MAX_VALUE) 

presuppone che il numero massimo di elementi, si può sempre incontrare, è Long.MAX_VALUE ma stream può avere più elementi di quello, potrebbero persino essere infiniti.

.limit(condition? aLimit: list.size()) 

quando la sorgente stream è list, sta rompendo la valutazione pigra di un flusso. In linea di principio, un'origine di flusso mutabile può essere legalmente modificata arbitrariamente fino al punto in cui inizia l'azione terminale. Il risultato rifletterà tutte le modifiche apportate fino a questo punto. Quando aggiungi un'operazione intermedia che incorpora list.size(), ovvero la dimensione effettiva dell'elenco a questo punto, le successive modifiche applicate alla raccolta tra questo punto e l'operazione del terminale potrebbero far sì che questo valore abbia un significato diverso rispetto al previsto "effettivamente senza limite" semantico.

Paragonare con “Non Interference” section of the API documentation:

Per fonti di flusso beneducati, la sorgente può essere modificato prima dell'operazione terminale inizia e tali modifiche si rifletterà negli elementi coperti. Ad esempio, si consideri il seguente codice:

List<String> l = new ArrayList(Arrays.asList("one", "two")); 
Stream<String> sl = l.stream(); 
l.add("three"); 
String s = sl.collect(joining(" ")); 

Prima un elenco viene creato che consiste di due stringhe: "uno"; e due". Quindi viene creato un flusso da quella lista. Successivamente l'elenco viene modificato aggiungendo una terza stringa: "tre". Infine, gli elementi del flusso vengono raccolti e uniti. Poiché la lista è stata modificata prima dell'inizio dell'operazione di raccolta del terminale, il risultato sarà una stringa di "uno due tre".

Ovviamente, questo è un raro caso d'angolo, in quanto normalmente un programmatore formerà un'intera pipeline di flusso senza modificare la raccolta di sorgenti nel mezzo. Tuttavia, il semantico rimane e potrebbe trasformarsi in un bug molto difficile da trovare quando si entra in un caso così angusto.

Inoltre, poiché non sono equivalenti, l'API del flusso non riconoscerà mai questi valori come "effettivamente senza limite". Anche specificare Long.MAX_VALUE implica che l'implementazione dello stream deve tracciare il numero di elementi elaborati per garantire che il limite sia stato rispettato. Pertanto, non aggiungere un'operazione limit può avere un significativo vantaggio in termini di prestazioni rispetto all'aggiunta di un limite con un numero che il programmatore si aspetta di non superare mai.

5

penso che la prima linea deve essere:

stream = stream.filter(e -> e.getTimestamp() < max); 

in modo che la vostra utilizzando il flusso restituito dal filtro nelle operazioni successive, piuttosto che il flusso originale.

5

Ci sono due modi per fare questo

// Do some stream stuff 
List<E> results = list.stream() 
        .filter(e -> e.getTimestamp() < max); 
        .limit(limit > 0 ? limit : list.size()) 
        .collect(Collectors.toList()); 

O

// Do some stream stuff 
stream = stream.filter(e -> e.getTimestamp() < max); 

// Limit the stream 
if (limit != -1) { 
    stream = stream.limit(limit); 
} 

// Collect stream to list 
List<E> results = stream.collect(Collectors.toList()); 

Poiché si tratta di funzionale programmazione si dovrebbe sempre lavorare sul risultato di ogni funzione. Dovresti evitare di modificare qualsiasi cosa in questo stile di programmazione e trattare tutto come se fosse immutabile se possibile.

Poiché sto riassegnando il valore del flusso prima che venga chiamata un'operazione terminale, il codice precedente è ancora un modo corretto per utilizzare i flussi Java 8?

Dovrebbe funzionare, tuttavia si legge come un mix di codifica imperativa e funzionale. Suggerisco di scriverlo come flusso fisso come da mia prima risposta.

+0

o '.limit (limite> 0? Limite: Long.MAX_VALUE)' nel caso in cui lo stream non sia basato su una raccolta. – zeroflagL

+0

@Holger Ciò che è sbagliato è che una copia e incollato il codice dalla domanda e ha dimenticato di eliminare il commento. Grazie per averlo raccolto. –