Java Language Usando i flussi

Esempio

Un Stream è una sequenza di elementi su cui è possibile eseguire operazioni di aggregazione sequenziali e parallele. Qualsiasi Stream dato può potenzialmente contenere una quantità illimitata di dati. Di conseguenza, i dati ricevuti da un Stream vengono elaborati singolarmente al loro arrivo, anziché eseguire l'elaborazione batch sui dati del tutto. Quando combinati con espressioni lambda forniscono un modo conciso per eseguire operazioni su sequenze di dati utilizzando un approccio funzionale.

Esempio: ( vedi funziona su Ideone )

Stream<String> fruitStream = Stream.of("apple", "banana", "pear", "kiwi", "orange");

fruitStream.filter(s -> s.contains("a"))
           .map(String::toUpperCase)
           .sorted()
           .forEach(System.out::println);

Produzione:

MELA
BANANA
ARANCIA
PERA

Le operazioni eseguite dal codice precedente possono essere riassunte come segue:

  1. Crea uno Stream<String> contenente un Stream ordinato di elementi String di Stream ordinati utilizzando il metodo statico di fabbrica Stream.of(values) .

  2. L'operazione filter() conserva solo gli elementi che corrispondono a un determinato predicato (gli elementi che vengono testati dal predicato restituiscono true). In questo caso, mantiene gli elementi contenenti una "a" . Il predicato è dato come espressione lambda .

  3. L'operazione map() trasforma ogni elemento usando una determinata funzione, chiamata mapper. In questo caso, ogni Fruit String viene associata alla sua versione String maiuscolo utilizzando il metodo method: String::toUppercase .

    Si noti che l'operazione map() restituirà un flusso con un diverso tipo generico se la funzione di mappatura restituisce un tipo diverso dal proprio parametro di input. Ad esempio su uno Stream<String> chiama .map(String::isEmpty) restituisce un Stream<Boolean>

  4. L'operazione sort sorted() ordina gli elementi del Stream base al loro ordinamento naturale (lessicograficamente, nel caso di String ).

  5. Infine, l'operazione forEach(action) esegue un'azione che agisce su ciascun elemento del Stream , passandolo a un consumatore . Nell'esempio, ogni elemento viene semplicemente stampato sulla console. Questa operazione è un'operazione terminale, rendendo impossibile l'operazione su di esso.

    Si noti che le operazioni definite sullo Stream vengono eseguite a causa dell'operazione del terminale. Senza un'operazione terminale, lo stream non viene elaborato. Gli stream non possono essere riutilizzati. Una volta che viene chiamata un'operazione terminale, l'oggetto Stream diventa inutilizzabile.

Operazioni concatenate

Le operazioni (come visto sopra) sono concatenate per formare ciò che può essere visto come una query sui dati.


Flussi di chiusura

Nota che un Stream generalmente non deve essere chiuso. È richiesto solo per chiudere i flussi che operano su canali IO. La maggior parte dei tipi di Stream non funziona su risorse e pertanto non richiede la chiusura.

L'interfaccia Stream estende AutoCloseable . Gli stream possono essere chiusi chiamando il metodo close o usando le istruzioni try-with-resource.

Un esempio di caso d'uso in cui un Stream dovrebbe essere chiuso è quando crei un Stream di linee da un file:

try (Stream<String> lines = Files.lines(Paths.get("somePath"))) {
    lines.forEach(System.out::println);
}

L'interfaccia Stream dichiara anche il metodo Stream.onClose() che consente di registrare i gestori Runnable che verranno chiamati quando lo stream viene chiuso. Un caso di utilizzo di esempio è dove il codice che produce un flusso deve sapere quando viene utilizzato per eseguire una pulizia.

public Stream<String>streamAndDelete(Path path) throws IOException {
    return Files.lines(path).onClose(() -> someClass.deletePath(path));
}

Il gestore di esecuzione verrà eseguito solo se viene chiamato il metodo close() , esplicitamente o implicitamente da un'istruzione try-with-resources.


Ordine di elaborazione

L'elaborazione di un oggetto Stream può essere sequenziale o parallela .

In una modalità sequenziale , gli elementi vengono elaborati nell'ordine della sorgente del Stream . Se il Stream è ordinato (come un'implementazione SortedMap o un List ), l'elaborazione è garantita per corrispondere all'ordine della fonte. In altri casi, tuttavia, è necessario prestare attenzione a non dipendere dall'ordinamento (si veda: l'ordine di iterazione di Java HashMap keySet() coerente? ).

Esempio:

List<Integer> integerList = Arrays.asList(0, 1, 2, 3, 42); 

// sequential 
long howManyOddNumbers = integerList.stream()
                                    .filter(e -> (e % 2) == 1)
                                    .count(); 

System.out.println(howManyOddNumbers); // Output: 2

Vivi su Ideone

La modalità parallela consente l'utilizzo di più thread su più core, ma non vi è alcuna garanzia dell'ordine in cui vengono elaborati gli elementi.

Se più metodi vengono richiamati su un Stream sequenziale, non tutti i metodi devono essere richiamati. Ad esempio, se un Stream viene filtrato e il numero di elementi è ridotto a uno, non verrà eseguita una chiamata successiva a un metodo come l' sort . Ciò può aumentare le prestazioni di un Stream sequenziale: un'ottimizzazione che non è possibile con un Stream parallelo.

Esempio:

// parallel
long howManyOddNumbersParallel = integerList.parallelStream()
                                            .filter(e -> (e % 2) == 1)
                                            .count();

System.out.println(howManyOddNumbersParallel); // Output: 2

Vivi su Ideone


Differenze da contenitori (o collezioni )

Mentre alcune azioni possono essere eseguite sia su Containers che su Stream, alla fine hanno scopi diversi e supportano diverse operazioni. I contenitori sono più focalizzati su come gli elementi sono memorizzati e su come è possibile accedere a tali elementi in modo efficiente. Un Stream , d'altra parte, non fornisce accesso diretto e manipolazione ai suoi elementi; è più dedicato al gruppo di oggetti come entità collettiva ed esegue operazioni su quell'entità nel suo complesso. Stream e Collection sono astrazioni di alto livello separate per questi scopi diversi.