Introduzione ai thread Java in Java 8
Java Streams, introdotti come parte di Java 8, vengono utilizzati per lavorare con raccolte di dati. Non sono di per sé una struttura dati, ma possono essere utilizzati per inserire informazioni da altre strutture dati ordinandole e concatenandole per produrre un risultato finale. Nota: è importante non confondere Stream e Thread, poiché in russo entrambi i termini sono spesso indicati nella stessa traduzione "flusso". Stream denota un oggetto per eseguire operazioni (il più delle volte trasferimento o archiviazione di dati), mentre Thread (traduzione letterale - thread) denota un oggetto che consente l'esecuzione di determinati codici di programma in parallelo con altri rami di codice. Poiché Stream non è una struttura dati separata, non modifica mai l'origine dati. I flussi Java hanno le seguenti funzionalità:-
Java Stream può essere utilizzato utilizzando il pacchetto "java.util.stream". Può essere importato in uno script utilizzando il codice:
import java.util.stream.* ;
Utilizzando questo codice, possiamo anche implementare facilmente diverse funzioni integrate in Java Stream.
-
Java Stream può accettare input da raccolte di dati come raccolte e array in Java.
-
Java Stream non richiede la modifica della struttura dei dati di input.
-
Java Stream non modifica l'origine. Invece, genera output utilizzando metodi di pipeline appropriati.
-
I Java Stream sono soggetti a operazioni intermedie e terminali, di cui parleremo nelle sezioni seguenti.
-
In Java Stream, le operazioni intermedie vengono eseguite in pipeline e si verificano in un formato di valutazione lazy. Terminano con le funzioni del terminale. Questo costituisce il formato di base per l'utilizzo di Java Stream.
Creazione di un flusso Java in Java 8
I thread Java possono essere creati in diversi modi:1. Creazione di un flusso vuoto utilizzando il metodo Stream.empty()
Puoi creare un flusso vuoto da utilizzare successivamente nel codice. Se utilizzi il metodo Stream.empty() , verrà generato uno stream vuoto, che non contiene valori. Questo flusso vuoto può tornare utile se vogliamo saltare un'eccezione del puntatore nullo in fase di esecuzione. Per fare ciò puoi utilizzare il seguente comando:Stream<String> str = Stream.empty();
L'istruzione precedente genererà uno stream vuoto denominato str senza elementi al suo interno. Per verificarlo basta controllare il numero o la dimensione dello stream utilizzando il termine str.count() . Per esempio,
System.out.println(str.count());
Di conseguenza, otteniamo 0 in uscita .
2. Crea un flusso utilizzando il metodo Stream.builder() con un'istanza Stream.Builder
Possiamo anche utilizzare Stream Builder per creare uno stream utilizzando il modello di progettazione del builder. È progettato per la costruzione passo-passo di oggetti. Vediamo come possiamo istanziare uno stream utilizzando Stream Builder .Stream.Builder<Integer> numBuilder = Stream.builder();
numBuilder.add(1).add(2).add( 3);
Stream<Integer> numStream = numBuilder.build();
Utilizzando questo codice, puoi creare uno stream denominato numStream contenente int elementi . Tutto avviene abbastanza velocemente grazie all'istanza Stream.Builder chiamata numBuilder che viene creata per prima.
3. Crea uno stream con i valori specificati utilizzando il metodo Stream.of()
Un altro modo per creare uno stream prevede l'utilizzo del metodo Stream.of() . Questo è un modo semplice per creare uno stream con determinati valori. Dichiara e inizializza anche il thread. Un esempio di utilizzo del metodo Stream.of() per creare uno stream:Stream<Integer> numStream = Stream.of(1, 2, 3);
Questo codice creerà uno stream contenente elementi int , proprio come abbiamo fatto nel metodo precedente utilizzando Stream.Builder . Qui abbiamo creato direttamente uno stream utilizzando Stream.of() con valori predefiniti [1, 2 e 3] .
4. Creare un flusso da un array esistente utilizzando il metodo Arrays.stream()
Un altro metodo comune per creare un thread prevede l'utilizzo di array in Java. Lo stream qui viene creato da un array esistente utilizzando il metodo Arrays.stream() . Tutti gli elementi dell'array vengono convertiti in elementi del flusso. Ecco un buon esempio:Integer[] arr = {1, 2, 3, 4, 5};
Stream<Integer> numStream = Arrays.stream(arr);
Questo codice genererà un numStream contenente il contenuto di un array chiamato arr, che è un array di numeri interi.
5. Unione di due flussi esistenti utilizzando il metodo Stream.concat()
Un altro metodo che può essere utilizzato per creare uno stream è il metodo Stream.concat() . Viene utilizzato per combinare due thread per creare un singolo thread. Entrambi i flussi sono combinati in ordine. Ciò significa che il primo thread viene prima, seguito dal secondo thread e così via. Un esempio di tale concatenazione è simile al seguente:Stream<Integer> numStream1 = Stream.of(1, 2, 3, 4, 5);
Stream<Integer> numStream2 = Stream.of(1, 2, 3);
Stream<Integer> combinedStream = Stream.concat( numStream1, numStream2);
L'istruzione precedente creerà uno stream finale denominato combinedStream contenente gli elementi del primo stream numStream1 e del secondo stream numStream2 uno per uno .
Tipi di operazioni con Java Stream
Come già accennato, con Java Stream in Java 8 è possibile eseguire due tipi di operazioni: intermedie e terminali. Diamo un'occhiata a ciascuno di essi in modo più dettagliato.Operazioni intermedie
Le operazioni intermedie generano un flusso di output e vengono eseguite solo quando incontrano un'operazione terminale. Ciò significa che le operazioni intermedie vengono eseguite pigramente, in pipeline e possono essere completate solo da un'operazione terminale. Imparerai a conoscere la valutazione pigra e il pipeline un po' più tardi. Esempi di operazioni intermedie sono i seguenti metodi: filter() , map() , Different() , peek() , sorted() e alcuni altri.Operazioni sui terminali
Le operazioni terminali completano l'esecuzione delle operazioni intermedie e restituiscono anche i risultati finali del flusso di output. Poiché le operazioni terminali segnalano la fine dell'esecuzione lenta e del pipelining, questo thread non può essere utilizzato nuovamente dopo aver subito un'operazione terminale. Esempi di operazioni da terminale sono i seguenti metodi: forEach() , collector() , count() , reduce() e così via.Esempi di operazioni con Java Stream
Operazioni intermedie
Ecco alcuni esempi di alcune operazioni intermedie che possono essere applicate a un Java Stream:filtro()
Questo metodo viene utilizzato per filtrare gli elementi da un flusso che corrispondono a un predicato specifico in Java. Questi elementi filtrati costituiscono quindi un nuovo flusso. Facciamo un esempio per capire meglio.Stream<Integer> numStream = Stream.of(43, 65, 1, 98, 63); List<Integer> even = numStream.filter(n -> n % 2 == 0) .collect(Collectors.toList()); System.out.println(even);
Conclusione:
carta geografica()
Questo metodo viene utilizzato per creare un nuovo flusso eseguendo funzioni mappate sugli elementi del flusso di input originale. Forse il nuovo flusso ha un tipo di dati diverso. L'esempio è simile al seguente:Stream<Integer> numStream = Stream.of(43, 65, 1, 98, 63); List<Integer> d = numStream.map(n -> n*2) .collect(Collectors.toList()); System.out.println(d);
Conclusione:
distinto()
Questo metodo viene utilizzato per recuperare solo singoli elementi in uno stream filtrando i duplicati. Un esempio dello stesso assomiglia a questo:Stream<Integer> numStream = Stream.of(43,65,1,98,63,63,1); List<Integer> numList = numStream.distinct() .collect(Collectors.toList()); System.out.println(numList);
Conclusione:
sbirciare()
Questo metodo viene utilizzato per tenere traccia delle modifiche intermedie prima di eseguire un'operazione sul terminale. Ciò significa che peek() può essere utilizzato per eseguire un'operazione su ciascun elemento di uno stream per creare uno stream su cui possono essere eseguite ulteriori operazioni intermedie.Stream<Integer> numStream = Stream.of(43, 65, 1, 98, 63); List<Integer> nList = numStream.map(n -> n*10) .peek(n->System.out.println("Mapped: "+ n)) .collect(Collectors.toList()); System.out.println(nList);
Conclusione:
smistato()
Il metodo sorted() viene utilizzato per ordinare gli elementi di uno stream. Per impostazione predefinita, ordina gli elementi in ordine crescente. È inoltre possibile specificare un ordinamento specifico come parametro. Un esempio di implementazione di questo metodo è simile al seguente:Stream<Integer> numStream = Stream.of(43, 65, 1, 98, 63); numStream.sorted().forEach(n -> System.out.println(n));
Conclusione:
Operazioni sui terminali
Ecco alcuni esempi di alcune operazioni del terminale che possono essere applicate ai flussi Java:per ciascuno()
Il metodo forEach() viene utilizzato per scorrere tutti gli elementi di uno stream ed eseguire la funzione su ciascun elemento uno per uno. Funziona come alternativa alle istruzioni di ciclo come for , while e altre. Esempio:Stream<Integer> numStream = Stream.of(43, 65, 1, 98, 63); numStream.forEach(n -> System.out.println(n));
Conclusione:
contare()
Il metodo count() viene utilizzato per recuperare il numero totale di elementi presenti nello stream. È simile al metodo size() , che viene spesso utilizzato per determinare il numero totale di elementi in una raccolta. Un esempio di utilizzo del metodo count() con Java Stream è il seguente:Stream<Integer> numStream = Stream.of(43, 65, 1, 98, 63); System.out.println(numStream.count());
Conclusione:
raccogliere()
Il metodo Collect() viene utilizzato per eseguire riduzioni mutabili di elementi del flusso. Può essere utilizzato per rimuovere contenuto da uno stream al termine dell'elaborazione. Utilizza la classe Collector per eseguire riduzioni .Stream<Integer> numStream = Stream.of(43, 65, 1, 98, 63); List<Integer> odd = numStream.filter(n -> n % 2 == 1) .collect(Collectors.toList()); System.out.println(odd);
Conclusione:
minimo() e massimo()
Il metodo min() , come suggerisce il nome, può essere utilizzato su uno stream per trovare l'elemento minimo al suo interno. Allo stesso modo, il metodo max() può essere utilizzato per trovare l'elemento massimo in uno stream. Cerchiamo di capire come possono essere utilizzati con un esempio:Stream<Integer> numStream = Stream.of(43, 65, 1, 98, 63); int smallest = numStream.min((m, n) -> Integer.compare(m, n)).get(); System.out.println("Smallest element: " + smallest);
numStream = Stream.of(43, 65, 1, 98, 63); int largest = numStream.max((m, n) -> Integer.compare(m, n)).get(); System.out.println("Largest element: " + largest);
Conclusione:
findAny() e findFirst()
findAny() restituisce qualsiasi elemento dello stream come Opzionale . Se il flusso è vuoto, restituirà anche un valore opzionale , che sarà vuoto. findFirst() restituisce il primo elemento dello stream come Opzionale . Come con il metodo findAny() , anche il metodo findFirst() restituisce un parametro Opzionale vuoto se il flusso corrispondente è vuoto. Diamo un'occhiata al seguente esempio basato su questi metodi:Stream<Integer> numStream = Stream.of(43, 65, 1, 98, 63); Optional<Integer> opt = numStream.findFirst();System.out.println(opt); numStream = Stream.empty(); opt = numStream.findAny();System.out.println(opt);
Conclusione:
allMatch() , anyMatch() e noneMatch()
Il metodo allMatch() viene utilizzato per verificare se tutti gli elementi in un flusso corrispondono a un determinato predicato e restituisce il valore booleano true se lo fanno, altrimenti restituisce false . Se il flusso è vuoto, restituisce true . Il metodo anyMatch() viene utilizzato per verificare se uno qualsiasi degli elementi in un flusso corrisponde a un determinato predicato. Restituisce vero se è così, falso altrimenti. Se il flusso è vuoto, restituisce false . Il metodo noneMatch() restituisce true se nessun elemento nello stream corrisponde al predicato e false altrimenti. Un esempio per illustrare questo è il seguente:Stream<Integer> numStream = Stream.of(43, 65, 1, 98, 63); boolean flag = numStream.allMatch(n -> n1); System.out.println(flag); numStream = Stream.of(43, 65, 1, 98, 63); flag = numStream.anyMatch(n -> n1); System.out.println(flag); numStream = Stream.of(43, 65, 1, 98, 63); flag = numStream.noneMatch(n -> n==1);System.out.println(flag);
Conclusione:
Valutazioni pigre in Java Stream
La valutazione pigra porta a ottimizzazioni quando si lavora con Java Streams in Java 8. Implicano principalmente il ritardo delle operazioni intermedie fino a quando non viene incontrata un'operazione terminale. La valutazione pigra ha il compito di prevenire inutili sprechi di risorse sui calcoli finché il risultato non è effettivamente necessario. Il flusso di output risultante dalle operazioni intermedie viene generato solo dopo il completamento dell'operazione terminale. La valutazione pigra funziona con tutte le operazioni intermedie nei flussi Java. Un uso molto utile della valutazione pigra si verifica quando si lavora con flussi infiniti. In questo modo si evitano molte elaborazioni non necessarie.Pipeline in Java Stream
Una pipeline in un Java Stream è costituita da un flusso di input, zero o più operazioni intermedie allineate una dopo l'altra e infine un'operazione terminale. Le operazioni intermedie in Java Streams vengono eseguite pigramente. Ciò rende inevitabili le operazioni intermedie in pipeline. Con le pipeline, che sono fondamentalmente operazioni intermedie combinate in ordine, diventa possibile l'esecuzione pigra. Le pipeline aiutano a tenere traccia delle operazioni intermedie che devono essere eseguite dopo che viene finalmente incontrata un'operazione terminale.Conclusione
Riassumiamo ora ciò che abbiamo imparato oggi. In questo articolo:- Abbiamo dato una rapida occhiata a cosa sono i Java Streams.
- Abbiamo quindi imparato molte tecniche diverse per creare thread Java in Java 8.
- Abbiamo appreso due tipi principali di operazioni (operazioni intermedie e operazioni terminali) che possono essere eseguite sui flussi Java.
- Abbiamo poi esaminato in dettaglio diversi esempi di operazioni sia intermedie che terminali.
- Alla fine abbiamo imparato di più sulla valutazione pigra e infine abbiamo imparato a conoscere il pipeline nei thread Java.
GO TO FULL VERSION