How to feed the ducks using Spliterator in Java?

Pradeesh Kumar
5 min readJan 1, 2023

--

Image by Stocksnap from Pixabay

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

TheSpliteratorwas 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);

--

--

Pradeesh Kumar
Pradeesh Kumar

Written by Pradeesh Kumar

Distributed Systems | Cloud Computing | Databases | Software Engineering

No responses yet