How to feed the ducks using Spliterator in Java?
Overview
Suppose you buy a bag of food so two children can feed the ducks at a riverside. To avoid arguments, you have an extra empty bag. You roughly take half the food out of the main bag and put it into the empty bag. The original bag now only contains the other half of the food. Now two children can feed the ducks independently and happily without any quarrels.
The Spliterator provides this level of control over processing. It starts with a Collection or a Stream and gets theSpliterator
object out of it — that's your bag of food. You call the methodtrySplit()
to take some food out of the bag. The rest of the food stays in the originalSpliterator
Object.
List<String> duckFood = List.of("sweetcorn", "lettuce", "peas", "seeds", "rice");
Spliterator<String> bag1 = duckFood.spliterator();
Spliterator<String> bag2 = bag1.trySplit();
bag1.forEachRemaining(System.out::println); // Prints: peas, seeds, rice
System.out.println();
bag2.forEachRemaining(System.out::println); // Prints: sweetcorn, lettuce
TheSpliterator
was introduced in Java 8, which can split and iterate a data source such as a Collection, Stream, or generator function, which can later be used for parallel processing. The goal of the Spliterator is to break down a large data set and later each chunk can be processed independently, maybe in a separate thread. Let's explore the usage, characteristics and methods of Spliterator in this article.
Methods
1. tryAdvance
The methodboolean tryAdvance(Consumer<? super T> consumer)
is used to iterate a Spliterator. This method accepts aConsumer
to consume an element at a time sequentially. It returns true if more elements are available otherwise returns false.
List<Integer> nums = List.of(1, 2, 3, 4, 5);
Spliterator<Integer> numsSpliterator = nums.spliterator();
while(numsSpliterator.tryAdvance(System.out::println)); // Prints 1, 2, 3, 4, 5
Use case: You just want to consume only two elements from the Spliterator.
List<Integer> nums = List.of(1, 2, 3, 4, 5);
Spliterator<Integer> numsSpliterator = nums.spliterator();
int count = 0;
while (count < 2 && numsSpliterator.tryAdvance(System.out::println)) {
count++;
}
// Prints 1, 2
2. forEachRemaining
The methodvoid forEachRemaining(Consumer<? super T> consumer)
is used for bulk iteration. Unlike tryAdvance, you cannot consume only a limited number of elements withforEachRemaining
. Instead, you will iterate all the elements.
List<Integer> nums = List.of(1, 2, 3, 4, 5);
Spliterator<Integer> numsSpliterator = nums.spliterator();
numsSpliterator.forEachRemaining(System.out::println); // Prints 1, 2, 3, 4, 5
2. trySplit
The methodSpliterator<T> trySplit()
tries to partition some elements of a Spliterator as another Spliterator. The original Spliterator will have the remaining elements. This is useful for parallel processing when you have a large Collection and partition then half and further smaller chunks and process them independently using multiple threads.
Note: repeated call totrySplit
eventually returnsnull
when the Spliterator cannot be split any further.
Simple Example:
List<Integer> nums = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// first will Contain: 1, 2, 3, 4, 5, 6, 7, 8, 9, 10
Spliterator<Integer> first = nums.spliterator();
// first may Contain: 6, 7, 8, 9, 10
// second may Contain: 1, 2, 3, 4, 5
Spliterator<Integer> second = first.trySplit();
// first may Contain: 8, 9, 10
// third may Contain: 6, 7
Spliterator<Integer> third = first.trySplit();
// first may Contain: 3, 4, 5
// third may Contain: 1, 2
Spliterator<Integer> fourth = second.trySplit();
first.forEachRemaining(System.out::println); // 8, 9, 10
second.forEachRemaining(System.out::println); // 3, 4, 5
third.forEachRemaining(System.out::println); // 6, 7
fourth.forEachRemaining(System.out::println); // 1, 2
Example 2: Split a list into two halves and iterate each partition in separate threads.
public static void main(String[] args) {
List<Integer> nums = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
Spliterator<Integer> first = nums.spliterator();
Spliterator<Integer> second = first.trySplit();
execute(first);
execute(second);
}
static void execute(Spliterator<Integer> spliterator) {
new Thread(() -> {
while (spliterator.tryAdvance(System.out::println)) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}).start();
}
3. estimatedSize
The methodlong estimatedSize()
returns an estimate of the number of elements that would be encountered by aforEachRemaining
traversal. It may returnLong.MAX_VALUE
if infinite, unknown, or too expensive to compute.
Spliterator.OfInt spliterator = IntStream.range(0, 449).spliterator();
Spliterator<Integer> second = spliterator.trySplit();
System.out.println(spliterator.estimateSize());
System.out.println(second.estimateSize());
4. characteristics
The methodint characteristics()
returns a set of characteristics of the underlying data source of this Spliterator. These will help the client to control or specialize the computation. For example, the characteristics of a Spliterator for a Collection
is SIZED
, for aSet
would beDISTINCT
, and a Spliterator for a SortedSet
would also report SORTED
. Characteristics are reported as a simple unioned bit set.
List of Characteristics
- ORDERED — Signifies that the order is defined for the elements.
- DISTINCT — Signifies that the elements are unique
- SORTED — Signifies that the elements follow sorted order. getComparator() method returns the comparator if the Spliterator has the SORTED characteristics
- SIZED — signifies that the value returned by estimatedSize() is finite.
- NONNULL — signifies that the elements are always not-null.
- IMMUTABLE — signifies that the element source cannot be modified.
- CONCURRENT — signifies that the element source can be safely modified concurrently.
- SUBSIZED — signifies that the Spliterator returned by trySplit() will be SIZED and SUBSIZED
Example — for List:
List<Integer> list = List.of(1, 2, 3, 4, 5);
Spliterator<Integer> listSpliterator = list.spliterator();
System.out.println(listSpliterator.hasCharacteristics(Spliterator.SIZED)); // true
System.out.println(listSpliterator.hasCharacteristics(Spliterator.SUBSIZED)); // true
System.out.println(listSpliterator.hasCharacteristics(Spliterator.ORDERED)); // true
System.out.println(listSpliterator.hasCharacteristics(Spliterator.SORTED)); // false
System.out.println(listSpliterator.hasCharacteristics(Spliterator.DISTINCT)); // false
System.out.println(listSpliterator.hasCharacteristics(Spliterator.NONNULL)); // false
System.out.println(listSpliterator.hasCharacteristics(Spliterator.CONCURRENT)); // false
System.out.println(listSpliterator.hasCharacteristics(Spliterator.IMMUTABLE)); // false
Example — for HashSet:
Set<Integer> set = Set.of(1, 2, 3, 4, 5);
Spliterator<Integer> spliterator = set.spliterator();
System.out.println(spliterator.hasCharacteristics(Spliterator.SIZED)); // true
System.out.println(spliterator.hasCharacteristics(Spliterator.SUBSIZED)); // true
System.out.println(spliterator.hasCharacteristics(Spliterator.ORDERED)); // false
System.out.println(spliterator.hasCharacteristics(Spliterator.SORTED)); // false
System.out.println(spliterator.hasCharacteristics(Spliterator.DISTINCT)); // true
System.out.println(spliterator.hasCharacteristics(Spliterator.NONNULL)); // false
System.out.println(spliterator.hasCharacteristics(Spliterator.CONCURRENT)); // false
System.out.println(spliterator.hasCharacteristics(Spliterator.IMMUTABLE)); // false
Example — for TreeSet:
SortedSet<Integer> sortedSet = new TreeSet<>(Set.of(1, 2, 3, 4, 5));
Spliterator<Integer> spliterator = sortedSet.spliterator();
System.out.println(spliterator.hasCharacteristics(Spliterator.SIZED)); // true
System.out.println(spliterator.hasCharacteristics(Spliterator.SUBSIZED)); // false
System.out.println(spliterator.hasCharacteristics(Spliterator.ORDERED)); // true
System.out.println(spliterator.hasCharacteristics(Spliterator.SORTED)); // true
System.out.println(spliterator.hasCharacteristics(Spliterator.DISTINCT)); // true
System.out.println(spliterator.hasCharacteristics(Spliterator.NONNULL)); // false
System.out.println(spliterator.hasCharacteristics(Spliterator.CONCURRENT)); // false
System.out.println(spliterator.hasCharacteristics(Spliterator.IMMUTABLE)); // false
Example — for Stream:
Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5);
Spliterator<Integer> spliterator = stream.spliterator();
System.out.println(spliterator.hasCharacteristics(Spliterator.SIZED)); // true
System.out.println(spliterator.hasCharacteristics(Spliterator.SUBSIZED)); // true
System.out.println(spliterator.hasCharacteristics(Spliterator.ORDERED)); // true
System.out.println(spliterator.hasCharacteristics(Spliterator.SORTED)); // false
System.out.println(spliterator.hasCharacteristics(Spliterator.DISTINCT)); // false
System.out.println(spliterator.hasCharacteristics(Spliterator.NONNULL)); // false
System.out.println(spliterator.hasCharacteristics(Spliterator.CONCURRENT)); // false
System.out.println(spliterator.hasCharacteristics(Spliterator.IMMUTABLE)); // true
Example — for ConcurrentHashMap
ConcurrentHashMap<Integer, Integer> map = new ConcurrentHashMap<>(Map.of(1, 1, 2, 2, 3, 3));
Spliterator<?> spliterator = map.entrySet().spliterator();
System.out.println(spliterator.hasCharacteristics(Spliterator.SIZED)); // false
System.out.println(spliterator.hasCharacteristics(Spliterator.SUBSIZED)); // false
System.out.println(spliterator.hasCharacteristics(Spliterator.ORDERED)); // false
System.out.println(spliterator.hasCharacteristics(Spliterator.SORTED)); // false
System.out.println(spliterator.hasCharacteristics(Spliterator.DISTINCT)); // true
System.out.println(spliterator.hasCharacteristics(Spliterator.NONNULL)); // true
System.out.println(spliterator.hasCharacteristics(Spliterator.CONCURRENT)); // true
System.out.println(spliterator.hasCharacteristics(Spliterator.IMMUTABLE)); // false
Example — for infinite random number generator stream
Stream<Integer> naturalNumbers = Stream.generate(() -> (int) (100 * Math.random()));
Spliterator<?> spliterator = naturalNumbers.spliterator();
System.out.println(spliterator.hasCharacteristics(Spliterator.SIZED)); // false
System.out.println(spliterator.hasCharacteristics(Spliterator.SUBSIZED)); // false
System.out.println(spliterator.hasCharacteristics(Spliterator.ORDERED)); // false
System.out.println(spliterator.hasCharacteristics(Spliterator.SORTED)); // false
System.out.println(spliterator.hasCharacteristics(Spliterator.DISTINCT)); // false
System.out.println(spliterator.hasCharacteristics(Spliterator.NONNULL)); // false
System.out.println(spliterator.hasCharacteristics(Spliterator.CONCURRENT)); // false
System.out.println(spliterator.hasCharacteristics(Spliterator.IMMUTABLE)); // true
5. hasCharacteristics
The method boolean hasCharacteristics(int characteristic)
returns true if the Spliterator has the specified characteristic.
6. getComparator
If this Spliterator’s source is SORTED
by a Comparator
, returns that Comparator
. If the source is SORTED
in the natural order, returns null
. Otherwise, if the source is not SORTED
, throws IllegalArgumentException
.
Support for Primitives
The Spliterator also supports primitives which include int, long and double.
The API for primitives:
- Spliterator.OfPrimitive is the parent interface for other primitive Spliterators.
- Spliterator.OfInt: The Spliterator for the int type
- Spliterator.OfDouble: The Spliterator for double type
- Spliterator.OfLong: The Spliterator for long type
Example:
Spliterator.OfInt intSpliterator = IntStream.of(1, 2, 3, 4, 5).spliterator();
IntConsumer consumer = System.out::println;
intSpliterator.forEachRemaining(consumer);