Collection e Stream in Java

Una interessante caratteristica di Java 8 è la possibilità di processare gli oggetti contenuti in una collection attraverso l’utilizzo di una nuovo strumento: gli Stream. Questa nuova astrazione non ha nulla a che vedere con gli stream utilizzati nella gestione dell’I/O ma sono molto più simili agli Iterator ma, diversamente, hanno lo scopo di consentire di elaborare gli oggetti della collezione utilizzando un approccio dichiarativo (simile all’SQL).

Uno stream, quindi, rappresenta una sequenza di oggetti ottenuti da una specifica sorgente ai quali possiamo applicare una sequenza di operazioni. Più formalmente uno stream ha le seguenti caratteristiche:

  • Consente l’accesso in modo sequenziale ad un insieme di elementi di un tipo specifico;
  • Gli elementi dello stream possono essere recuperati da una collezione, da un array o da una operazione di I/O;
  • Lo stream supporta operazioni di aggregazione (che vedremo nel seguito);
  • Molte operazioni sugli stream restituiscono stream, quindi possono essere concatenate.

Stream Processing

Per poter operare sugli stream è innanzitutto necessario ottenerne uno. Allo scopo è stato introdotto un metodo specifico nell’interfaccia Collection:

Una volta ottenuto lo stream dalla collection è possibile procedere all’elaborazione dei suoi elementi attraverso un processo a due fasi:

  1. Configurazione: consiste in un professo di filtraggio o mappatura degli elementi;
  2. Elaborazione: esegue l’operazione specificata sugli elementi filtrati o mappati.

Le operazioni di configurazione non vengono eseguite fino a quando non è avviata l’elaborazione.

Operazioni di Configurazione

Filtraggio

Per filtrare lo stream è sufficiente utilizzare il metodo filter() che riceve in ingresso un oggetto che implementa l’interfaccia java.util.function.Predicate che definisce un solo metodo con firma boolean test(T t), che riceve in ingresso un item della collezione e determina se filtrarlo o meno. Solo gli oggetti che passano il test vengono processati nelle successive fasi di elaborazione.

Nell’esempio sono filtrate tutte le stringhe della collezione che iniziano con il carattere u. Come già detto l’operazione è solo configurata ma non sarà eseguita fino all’applicazione di un metodo di elaborazione.

Mapping

L’operazione di mapping consiste nel mappare gli oggetti della collezione in altri oggetti da essi derivati. Allo scopo l’oggetto Stream espone il metodo map() che riceve in ingresso un oggetto che implementa l’interfaccia java.util.function.Function. Tale interfaccia definisce il metodo apply() che esegue il lavoro di mapping vero e proprio. Nell’esempio ogni item è pappato in un intero che rappresenta la lunghezza della stringa.

Come nel caso del filtraggio l’operazione di mapping è solo configurata sullo stream ma non sarà eseguita fino all’applicazione di un metodo di elaborazione.

Limit

Consente di ridurre la dimensione dello stream ad un valore massimo specificato eliminando gli item in eccesso.

Sorted

Il metodo consiste di ordinare lo stream. Può essere utilizzato senza parametri e quindi l’ordinamento sarà quello naturale associato al tipo di elementi dello strema, oppure può accettare un oggetto di tipo Comparator.

Operazioni di Elaborazione

Collect

La principale operazione di elaborazione è la collect(), che riceve in input un oggetto Collector. Tali oggetti sono utilizzati per combinare gli elementi appartenenti ad uno stream. Alcuni esempi di operazioni che possono essere eseguite sono:

  • inserimento degli elementi in una nuova lista;
  • inserimento degli elementi in un set;
  • conversione degli elementi in stringhe e concatenazione;
  • raggruppamento degli elementi;
  • calcolo della somma dei valori assunti da una proprietà degli elementi;
  • partizionamento degli elementi;

Fortunatamente l’oggetto Collectors espone metodi statici che restituiscono molti dei Collector più utilizzati, fermo restando la possibilità per l’utente di implementare il proprio collector.

Min e Max

Tali operazioni restituiscono i valore minimo o massimo tra gli elementi presenti nello stream. I due operatori richiedono in input on oggetto java.util.Comparator e restituiscono un oggetto java.util.Optional che è un oggetto contenitore che può o meno contenere un valore nullo.

Count

Semplicemente restituisce il numero di elementi nello stream dopo che è stato filtrato.

Reduce

L’operazione di reduce consente di ridurre gli elementi di uno stream in un singolo oggetto. Il metodo reduce() richiede come parametro di input un oggetto che implementa l’interfaccia BinaryOperator, la quale definisce un metodo apply(). La firma di tale metodo prevede in input un parametro accumulatore ed un item dello stream e restituisce un oggetto Optional, nel caso in cui lo stream sia vuoto e la riduzione restituisca quindi un oggetto null.

Esiste un’altro metodo reduce() che prevede in input anche il valore iniziale dell’accumulatore e che quindi non restituirà un oggetto Optional, ma in caso di stream vuoto restituisce direttamente tale valore.

Codice Sorgente

La classe che implementa i metodi indicati è scaricabile qui MainStream.java.

 

How useful was this post?

Click on a star to rate it!

Average rating 4 / 5. Vote count: 4

No votes so far! Be the first to rate this post.

As you found this post useful...

Follow us on social media!