1. Elenco dei metodi della Stream
classe
La Stream
classe è stata creata per semplificare la costruzione di catene di flussi di dati. Per ottenere ciò, la Stream<T>
classe dispone di metodi che restituiscono nuovi Stream
oggetti.
Ciascuno di questi flussi di dati esegue una semplice azione, ma se li combini in catene e aggiungi interessanti funzioni lambda , allora hai un potente meccanismo per generare l'output che desideri. Presto lo vedrai di persona.
Ecco i metodi della Stream
classe (solo i più basilari):
Metodi | Descrizione |
---|---|
|
Crea un flusso da un insieme di oggetti |
|
Genera un flusso in base alla regola specificata |
|
Concatena due flussi |
|
Filtra i dati, trasmettendo solo i dati che corrispondono alla regola specificata |
|
Rimuove i duplicati. Non passa i dati che sono già stati rilevati |
|
Ordina i dati |
|
Esegue un'azione su ogni elemento nel flusso |
|
Restituisce un flusso troncato in modo che non superi il limite specificato |
|
Salta i primi n elementi |
|
Converte i dati da un tipo all'altro |
|
Converte i dati da un tipo all'altro |
|
Verifica se nel flusso è presente almeno un elemento che corrisponde alla regola specificata |
|
Controlla se tutti gli elementi nel flusso corrispondono alla regola specificata |
|
Controlla se nessuno degli elementi nel flusso corrisponde alla regola specificata |
|
Restituisce il primo elemento trovato che corrisponde alla regola |
|
Restituisce qualsiasi elemento nel flusso che corrisponde alla regola |
|
Cerca l'elemento minimo nel flusso di dati |
|
Restituisce l'elemento massimo nel flusso di dati |
|
Restituisce il numero di elementi nel flusso di dati |
|
Legge tutti i dati dal flusso e li restituisce come raccolta |
2. Operazioni intermedie e terminali della Stream
classe
Come puoi vedere, non tutti i metodi nella tabella sopra restituiscono un file Stream
. Ciò è legato al fatto che i metodi della Stream
classe possono essere suddivisi in metodi intermedi (noti anche come non terminali ) e metodi terminali .
Metodi intermedi
I metodi intermedi restituiscono un oggetto che implementa l' Stream
interfaccia e possono essere concatenati insieme.
Metodi terminali
I metodi terminali restituiscono un valore diverso da Stream
.
Pipeline di chiamata al metodo
Pertanto, è possibile creare una pipeline di flusso costituita da un numero qualsiasi di metodi intermedi e una singola chiamata di metodo terminale alla fine. Questo approccio consente di implementare una logica piuttosto complessa, aumentando al contempo la leggibilità del codice.

I dati all'interno di un flusso di dati non cambiano affatto. Una catena di metodi intermedi è un modo semplice (dichiarativo) per specificare una pipeline di elaborazione dati che verrà eseguita dopo la chiamata al metodo terminale.
In altre parole, se il metodo terminale non viene chiamato, i dati nel flusso di dati non vengono elaborati in alcun modo. Solo dopo che il metodo terminale è stato chiamato, i dati iniziano a essere elaborati in base alle regole specificate nella pipeline del flusso.
stream()
.intemediateOperation1()
.intemediateOperation2()
...
.intemediateOperationN()
.terminalOperation();
Confronto tra metodi intermedi e terminali:
intermedio | terminale | |
---|---|---|
Tipo di ritorno | Stream |
non unStream |
Può essere combinato con più metodi dello stesso tipo per formare una pipeline | SÌ | NO |
Numero di metodi in una singola pipeline | Qualunque | non più di uno |
Produce il risultato finale | NO | SÌ |
Avvia l'elaborazione dei dati nel flusso | NO | SÌ |
Diamo un'occhiata a un esempio.
Supponiamo di avere un club per gli amanti degli animali. Domani il club celebra il Ginger Cat Day. Il club ha proprietari di animali domestici, ognuno dei quali ha un elenco di animali domestici. Non sono limitati ai gatti.
Compito: devi identificare tutti i nomi di tutti i gatti rossi per creare per loro biglietti di auguri personalizzati per la "vacanza professionale" di domani. I biglietti di auguri dovrebbero essere ordinati per età del gatto, dal più vecchio al più giovane.
Innanzitutto, forniamo alcune classi per aiutare a risolvere questo compito:
public enum Color {
WHITE,
BLACK,
DARK_GREY,
LIGHT_GREY,
FOXY,
GREEN,
YELLOW,
BLUE,
MAGENTA
}
public abstract class Animal {
private String name;
private Color color;
private int age;
public Animal(String name, Color color, int age) {
this.name = name;
this.color = color;
this.age = age;
}
public String getName() {
return name;
}
public Color getColor() {
return color;
}
public int getAge() {
return age;
}
}
public class Cat extends Animal {
public Cat(String name, Color color, int age) {
super(name, color, age);
}
}
public class Dog extends Animal {
public Dog(String name, Color color, int age) {
super(name, color, age);
}
}
public class Parrot extends Animal {
public Parrot(String name, Color color, int age) {
super(name, color, age);
}
}
public class Pig extends Animal {
public Pig(String name, Color color, int age) {
super(name, color, age);
}
}
public class Snake extends Animal {
public Snake(String name, Color color, int age) {
super(name, color, age);
}
}
public class Owner {
private String name;
private List<Animal> pets = new ArrayList<>();
public Owner(String name) {
this.name = name;
}
public List<Animal> getPets() {
return pets;
}
}
Ora diamo un'occhiata alla Selector
classe, dove verrà effettuata la selezione in base ai criteri specificati:
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
public class Selector {
private static List<Owner> owners;
private static void initData() {
final Owner owner1 = new Owner("Ronan Turner");
owner1.getPets().addAll(List.of(
new Cat("Baron", Color.BLACK, 3),
new Cat("Sultan", Color.DARK_GREY, 4),
new Dog("Elsa", Color.WHITE, 0)
));
final Owner owner2 = new Owner("Scarlet Murray");
owner2.getPets().addAll(List.of(
new Cat("Ginger", Color.FOXY, 7),
new Cat("Oscar", Color.FOXY, 5),
new Parrot("Admiral", Color.BLUE, 3)
));
final Owner owner3 = new Owner("Felicity Mason");
owner3.getPets().addAll(List.of(
new Dog("Arnold", Color.FOXY, 3),
new Pig("Vacuum Cleaner", Color.LIGHT_GREY, 8)
));
final Owner owner4 = new Owner("Mitchell Stone");
owner4.getPets().addAll(List.of(
new Snake("Mr. Boa", Color.DARK_GREY, 2)
));
final Owner owner5 = new Owner("Jonathan Snyder");
owner5.getPets().addAll(List.of(
new Cat("Fisher", Color.BLACK, 16),
new Cat("Zorro", Color.FOXY, 14),
new Cat("Margo", Color.WHITE, 3),
new Cat("Brawler", Color.DARK_GREY, 1)
));
owners = List.of(owner1, owner2, owner3, owner4, owner5);
}
}
Resta da aggiungere codice al main
metodo. Attualmente, chiamiamo prima il initData()
metodo, che popola l'elenco dei proprietari di animali domestici nel club. Quindi selezioniamo i nomi dei gatti rossi ordinati per età in ordine decrescente.
Innanzitutto, diamo un'occhiata al codice che non utilizza i flussi per risolvere questo compito:
public static void main(String[] args) {
initData();
List<String> findNames = new ArrayList<>();
List<Cat> findCats = new ArrayList<>();
for (Owner owner : owners) {
for (Animal pet : owner.getPets()) {
if (Cat.class.equals(pet.getClass()) && Color.FOXY == pet.getColor()) {
findCats.add((Cat) pet);
}
}
}
Collections.sort(findCats, new Comparator<Cat>() {
public int compare(Cat o1, Cat o2) {
return o2.getAge() - o1.getAge();
}
});
for (Cat cat : findCats) {
findNames.add(cat.getName());
}
findNames.forEach(System.out::println);
}
Ora diamo un'occhiata a un'alternativa:
public static void main(String[] args) {
initData();
final List<String> findNames = owners.stream()
.flatMap(owner -> owner.getPets().stream())
.filter(pet -> Cat.class.equals(pet.getClass()))
.filter(cat -> Color.FOXY == cat.getColor())
.sorted((o1, o2) -> o2.getAge() - o1.getAge())
.map(Animal::getName)
.collect(Collectors.toList());
findNames.forEach(System.out::println);
}
Come puoi vedere, il codice è molto più compatto. Inoltre, ogni riga della pipeline del flusso è una singola azione, quindi possono essere lette come frasi in inglese:
|
Passa da a Stream<Owner> ad aStream<Pet> |
|
Mantieni solo i gatti nel flusso di dati |
|
Mantieni solo i gatti rossi nel flusso di dati |
|
Ordina per età in ordine decrescente |
|
Ottieni i nomi |
|
Inserisci il risultato in un elenco |
3. Creazione di flussi
La Stream
classe ha tre metodi che non abbiamo ancora trattato. Lo scopo di questi tre metodi è creare nuovi thread.
Stream<T>.of(T obj)
metodo
Il of()
metodo crea un flusso costituito da un singolo elemento. Questo di solito è necessario quando, diciamo, una funzione prende un Stream<T>
oggetto come argomento, ma tu hai solo un T
oggetto. Quindi puoi facilmente e semplicemente utilizzare il of()
metodo per ottenere un flusso costituito da un singolo elemento .
Esempio:
Stream<Integer> stream = Stream.of(1);
Stream<T> Stream.of(T obj1, T obj2, T obj3, ...)
metodo
Il of()
metodo crea un flusso costituito da elementi passati . È consentito qualsiasi numero di elementi. Esempio:
Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5);
Stream<T> Stream.generate(Supplier<T> obj)
metodo
Il generate()
metodo consente di impostare una regola che verrà utilizzata per generare l'elemento successivo del flusso quando viene richiesto. Ad esempio, puoi dare un numero casuale ogni volta.
Esempio:
Stream<Double> s = Stream.generate(Math::random);
Stream<T> Stream.concat(Stream<T> a, Stream<T> b)
metodo
Il concat()
metodo concatena i due flussi passati in uno . Quando i dati vengono letti, vengono letti prima dal primo flusso e poi dal secondo. Esempio:
Stream<Integer> stream1 = Stream.of(1, 2, 3, 4, 5);
Stream<Integer> stream2 = Stream.of(10, 11, 12, 13, 14);
Stream<Integer> result = Stream.concat(stream1, stream2);
4. Filtraggio dei dati
Altri 6 metodi creano nuovi flussi di dati, consentendo di combinare i flussi in catene (o pipeline) di varia complessità.
Stream<T> filter(Predicate<T>)
metodo
Questo metodo restituisce un nuovo flusso di dati che filtra il flusso di dati di origine in base alla regola passata . Il metodo deve essere chiamato su un oggetto il cui tipo è Stream<T>
.
È possibile specificare la regola di filtro utilizzando una funzione lambda , che il compilatore convertirà quindi in un Predicate<T>
oggetto.
Esempi:
Flussi incatenati | Spiegazione |
---|---|
|
Conserva solo i numeri inferiori a tre |
|
Conserva solo i numeri maggiori di zero |
Stream<T> sorted(Comparator<T>)
metodo
Questo metodo restituisce un nuovo flusso di dati che ordina i dati nel flusso di origine . Passi in un comparator , che imposta le regole per confrontare due elementi del flusso di dati.
Stream<T> distinct()
metodo
Questo metodo restituisce un nuovo flusso di dati che contiene solo gli elementi univoci nel flusso di dati di origine . Tutti i dati duplicati vengono eliminati. Esempio:
Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5, 2, 2, 2, 3, 4);
Stream<Integer> stream2 = stream.distinct(); // 1, 2, 3, 4, 5
Stream<T> peek(Consumer<T>)
metodo
Questo metodo restituisce un nuovo flusso di dati , sebbene i dati in esso contenuti siano gli stessi del flusso di origine. Ma quando l'elemento successivo viene richiesto dallo stream, la funzione che hai passato al peek()
metodo viene chiamata con esso.
Se passi la funzione System.out::println
al peek()
metodo, tutti gli oggetti verranno visualizzati quando passeranno attraverso il flusso.
Stream<T> limit(int n)
metodo
Questo metodo restituisce un nuovo flusso di dati che contiene solo i primi n
elementi nel flusso di dati di origine . Tutti gli altri dati vengono eliminati. Esempio:
Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5, 2, 2, 2, 3, 4);
Stream<Integer> stream2 = stream.limit(3); // 1, 2, 3
Stream<T> skip(int n)
metodo
Questo metodo restituisce un nuovo flusso di dati che contiene tutti gli stessi elementi del flusso di origine , ma salta (ignora) i primi n
elementi. Esempio:
Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5, 2, 2, 2, 3, 4);
Stream<Integer> stream2 = stream.skip(3); // 4, 5, 2, 2, 2, 3, 4
GO TO FULL VERSION