Iterators in Java are used to traverse the elements of a given source. Spliterator in Java is one among four available Java Iterators – Iterator, Enumeration, ListIterator, and the Spliterator. It is an interface available in the java.util package.
Why use Spliterator?
Java Spliterator offers us several advantages:
Supports parallel programming
We can use it for both the sequential and parallel processing of data items tryAdvance() method combines both next() and the hasNext() operations of a simple Iterator and so offers the better performance. Also, it’s important to realize that Spliterator works fine for both Collection and Stream sources, but not with map implementations as the source. Spliterator was the first introduced in Java 8 to support parallel programming. However, we can use it for both the sequential and parallel processing of the data items. To obtain an instance of the Java Spliterator, we’ll use the spliterator() method:
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
Spliterator splitr = list.spliterator();
We can think of Java Spliterator as Spliterator = Splitting + Iteration
Spliterator Characteristics:
A Spliterator interface defines the integral constants representing its characteristics. Our instance can have one or more of below eight characteristics:
SIZED – capable of returning the exact number of the elements in the source when we invoke estimateSize() method
SUBSIZED – When we split the instance using the trySplit() and obtain SIZED SplitIterators as well
ORDERED – iterating over the ordered sequence
SORTED – iterating over the sorted sequence
NONNULL – source guarantees to have not the null values
DISTINCT – no duplicates exist in the source sequence
IMMUTABLE – if we can’t structurally modify element source
CONCURRENT – element source can be safely concurrently modified
We can use the int characteristics() method to query the characteristics of our Spliterator instance. It returns the OR’ed value of all of the qualifying characteristic values for our Spliterator. For our defined splitr, we’ll have:
int charactersticsORed = splitr.characteristics();
hasCharacteristics():
We can use the boolean hasCharacteristics(int characteristic) method to check if our instance has a given characteristic or not:
boolean isSized = splitr.hasCharacteristics(Spliterator.SIZED); //true
boolean isSorted = splitr.hasCharacteristics(Spliterator.SORTED); //false
boolean isNonNull = splitr.hasCharacteristics(Spliterator.NONNULL); //false
estimateSize():
The estimateSize() method returns the estimated number of elements left to iterate over. It returns Long.MAX_VALUE if the value is infinite, unknown, or too expensive to compute. For the SIZED Spliterator, it returns a value that exactly corresponds to the number of the elements that would be encountered in a successful traversal:
long estimatedSize = splitr.estimateSize(); // 5
getExactSizeIfKnown():
It’s just a convenience method which returns the estimateSize() if it’s a SIZED Spliterator or else returns -1:
long size = splitr.getExactSizeIfKnown(); // 5
tryAdvance():
The signature of tryAdvance() method looks like:
default boolean tryAdvance(Consumer<? super T> action);
The tryAdvance() method in the Spliterator combines the hasNext() and next() operators present in a basic Iterator. So if a remaining element exists, it performs a given action on it, returning true; else returns false. In the other words, it acts on the next element in the sequence and then advances the iterator.
while(splitr.tryAdvance((item) -> System.out.println(item)));
If we have the ORDERED Spliterator, the action is performed on the next element in the encounter order.
forEachRemaining():
The forEachRemaining(Consumer<? superT> action) method performs the given action for each remaining element, sequentially in current thread, until all the elements have been processed or the action throws an exception:
splitr.forEachRemaining(item -> System.out.println(item));
The current default implementation repeatedly invokes tryAdvance() until it returns the false.
trySplit():
If partitioning is possible, trySplit() method splits the invoking Spliterator and returns the reference to the Spliterator covering elements that won’t be covered by this Spliterator upon return from this method. Otherwise, it returns the null. So after a successful split, the original Spliterator will iterate over one portion of the sequence and the returned Spliterator over the other portion of it. Also, the returned Spliterator covers a strict prefix of the elements for an initial ORDERED Spliterator (Eg: over a List):
// trySplit() method over ORDERED splitr
Spliterator<Integer> splitrNew = splitr.trySplit();
// Elements in our splitrNew = {1, 2, 3}
if(splitrNew != null) {
splitrNew.forEachRemaining((n) -> System.out.println(n)); // 1, 2
}
// Elements in our splitr - {4 , 5}
splitr.forEachRemaining((n) -> System.out.println(n)); // 4,5
Unless our original Spliterator represents the infinite sequence, repeated calls to trySplit() must eventually return the null.
getComparator():
If we have the Spliterator SORTED by a Comparator, it returns that Comparator. Or else it returns the null If the source is sorted in a natural order. For the source that isn’t SORTED, it will throw an IllegalStateException.
So for our example, we have: