Introduzione agli Spliterator in Java
Spliterator
è un tipo di iteratore introdotto in JDK 8 che supporta l'iterazione parallela.- Fornisce metodi per iterare su sequenze di elementi, combinando le operazioni
hasNext
enext
in un unico metodo. - Utilizza l'interfaccia
Consumer
per applicare azioni agli elementi durante l'iterazione. - Le caratteristiche di
Spliterator
possono essere utilizzate per ottimizzare l'iterazione e la programmazione parallela. - Esistono sotto-interfacce per tipi primitivi come
Spliterator.OfDouble
,Spliterator.OfInt
, eSpliterator.OfLong
.
Spliterator
JDK 8 ha aggiunto un altro tipo di iteratore chiamato spliterator che è definito dall'interfaccia Spliterator
.
Uno spliterator percorre una sequenza di elementi, e in questo senso, è simile agli iteratori descritti nella lezione precedente. Tuttavia, le tecniche necessarie per utilizzarlo differiscono.
Inoltre, offre sostanzialmente più funzionalità rispetto a Iterator
o ListIterator
. Forse l'aspetto più importante di Spliterator
è la sua capacità di fornire supporto per l'iterazione parallela di porzioni della sequenza. Quindi, Spliterator
supporta la programmazione parallela. Studieremo in dettaglio la programmazione parallela e concorrente nelle prossime lezioni.
Tuttavia, è possibile utilizzare Spliterator
anche se non si utilizzerà l'esecuzione parallela. Un motivo per cui si potrebbe volerlo fare è perché offre un approccio semplificato che combina le operazioni hasNext
e next
in un unico metodo.
Spliterator
è un'interfaccia generica che è dichiarata così:
interface Spliterator<T>
Qui, T
è il tipo di elementi su cui si itera. Spliterator
dichiara i metodi mostrati nella tabella che segue:
Metodo | Descrizione |
---|---|
int characteristics() |
Restituisce le caratteristiche dello spliterator invocante codificate in un intero. |
long estimateSize() |
Stima il numero di elementi rimasti da iterare e restituisce il risultato. Restituisce Long.MAX_VALUE se il conteggio non può essere ottenuto per qualsiasi motivo. |
default void forEachRemaining(Consumer<? super T> azione) |
Applica azione a ogni elemento non processato nella sorgente dati. |
default Comparator<? super T> getComparator() |
Restituisce il comparatore utilizzato dallo spliterator invocante o null se viene utilizzato l'ordinamento naturale. Se la sequenza è disordinata, viene lanciata IllegalStateException . |
default long getExactSizeIfKnown() |
Se lo spliterator invocante è dimensionato, restituisce il numero di elementi rimasti da iterare. Restituisce –1 altrimenti. |
default boolean hasCharacteristics(int val) |
Restituisce true se lo spliterator invocante ha le caratteristiche passate in val. Restituisce false altrimenti. |
boolean tryAdvance(Consumer<? super T> azione) |
Esegue azione sull'elemento successivo nell'iterazione. Restituisce true se c'è un elemento successivo. Restituisce false se non rimangono elementi. |
Spliterator<T> trySplit() |
Se possibile, divide lo spliterator invocante, restituendo un riferimento a un nuovo spliterator per la partizione. Altrimenti, restituisce null . Quindi, se ha successo, lo spliterator originale itera su una porzione della sequenza e lo spliterator restituito itera sull'altra porzione. |
Utilizzare Spliterator
per compiti di iterazione di base è abbastanza facile: basta chiamare tryAdvance()
finché non restituisce false
. Se applichiamo la stessa azione a ogni elemento nella sequenza, forEachRemaining()
offre un'alternativa semplificata. In entrambi i casi, l'azione che si verificherà con ogni iterazione è definita da ciò che l'oggetto Consumer
fa con ogni elemento. Consumer
è un'interfaccia funzionale che applica un'azione a un oggetto. È un'interfaccia funzionale generica dichiarata in java.util.function
. Consumer
specifica solo un metodo astratto, accept()
, che è mostrato qui:
void accept(T objRef)
Nel caso di tryAdvance()
, ogni iterazione passa l'elemento successivo nella sequenza a objRef
. Spesso, il modo più facile per implementare Consumer
è tramite l'uso di un'espressione lambda.
Il seguente programma fornisce un semplice esempio di Spliterator
. Notate che il programma dimostra sia tryAdvance()
che forEachRemaining()
. Si noti anche come questi metodi combinano le azioni dei metodi next()
e hasNext()
di Iterator
in una singola chiamata.
// Una semplice dimostrazione di Spliterator.
import java.util.*;
class DemoSpliterator {
public static void main(String[] args) {
// Crea un array list per double.
ArrayList<Double> valori = new ArrayList<>();
// Aggiungi valori all'array list.
valori.add(1.0);
valori.add(2.0);
valori.add(3.0);
valori.add(4.0);
valori.add(5.0);
// Usa tryAdvance() per visualizzare i contenuti di valori.
System.out.print("Contenuti di valori:\n");
Spliterator<Double> spltitr = valori.spliterator();
while (spltitr.tryAdvance((n) -> System.out.println(n)))
;
System.out.println();
// Crea una nuova lista che contiene radici quadrate.
spltitr = valori.spliterator();
ArrayList<Double> radiciQuadrate = new ArrayList<>();
while (spltitr.tryAdvance((n) -> radiciQuadrate.add(Math.sqrt(n))))
;
// Usa forEachRemaining() per visualizzare
// i contenuti di radiciQuadrate.
System.out.print("Contenuti di radiciQuadrate:\n");
spltitr = radiciQuadrate.spliterator();
spltitr.forEachRemaining((n) -> System.out.println(n));
System.out.println();
}
}
L'output è mostrato qui:
Contenuti di valori:
1.0
2.0
3.0
4.0
5.0
Contenuti di radiciQuadrate:
1.0
1.4142135623730951
1.7320508075688772
2.0
2.23606797749979
Sebbene questo programma dimostri i meccanismi dell'utilizzo di Spliterator
, non rivela il suo pieno potere. Come menzionato, il massimo beneficio di Spliterator
si trova in situazioni che coinvolgono l'elaborazione parallela.
Nella tabella di sopra, si notino i metodi characteristics()
e hasCharacteristics()
.
Ogni Spliterator
ha un insieme di attributi, chiamati caratteristiche, associati ad esso. Questi sono definiti da campi int
statici in Spliterator
, come SORTED
, DISTINCT
, SIZED
, e IMMUTABLE
, per citarne alcuni. È possibile ottenere le caratteristiche chiamando characteristics()
. È possibile determinare se una caratteristica è presente chiamando hasCharacteristics()
. Spesso, non si ha bisogno di accedere alle caratteristiche di uno Spliterator
, ma in alcuni casi, possono aiutare a creare codice efficiente e resiliente.
Ritorneremo sugli Spliterator
nelle prossime lezioni.
Ci sono diverse sotto-interfacce annidate di Spliterator
progettate per l'uso con i tipi primitivi double
, int
, e long
. Queste sono chiamate Spliterator.OfDouble
, Spliterator.OfInt
, e Spliterator.OfLong
. C'è anche una versione generalizzata chiamata Spliterator.OfPrimitive()
, che offre flessibilità aggiuntiva e serve come super-interfaccia delle suddette.