Spliterator
Spliterator (Splittable Iterator) is Java 8's mechanism for parallel and sequential element traversal of sources such as collections, arrays, I/O channels, and generators. Where Iterator is purely sequential and has no concept of size or characteristics, Spliterator carries metadata about its source — estimated size, encounter order, element uniqueness, nullability, and more — enabling the Stream API and fork/join framework to make intelligent decisions about parallelism. A Spliterator can split itself into two halves via trySplit(), allowing one half to be processed on a separate thread while the other continues locally. This entry covers all Spliterator methods, the eight characteristic flags, trySplit semantics and balancing, the four functional traversal methods, how to implement a custom Spliterator, and how the Stream pipeline uses Spliterators under the hood.
Spliterator Methods and Traversal
// ── Basic Spliterator usage ───────────────────────────────────────────
List<String> list = List.of("alpha", "beta", "gamma", "delta", "epsilon");
Spliterator<String> spliterator = list.spliterator();
System.out.println(spliterator.estimateSize()); // 5 (exact, SIZED is set)
System.out.println(spliterator.getExactSizeIfKnown()); // 5
// tryAdvance — process one element at a time
spliterator.tryAdvance(s -> System.out.println("First: " + s)); // First: alpha
// forEachRemaining — consume all remaining elements
spliterator.forEachRemaining(s -> System.out.println("Rest: " + s));
// Rest: beta
// Rest: gamma
// Rest: delta
// Rest: epsilon
// ── trySplit — manual parallel split ─────────────────────────────────
List<Integer> numbers = new ArrayList<>(
List.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10));
Spliterator<Integer> original = numbers.spliterator();
Spliterator<Integer> prefix = original.trySplit(); // ~first half
System.out.println("prefix size: " + prefix.estimateSize()); // 5
System.out.println("remaining size:" + original.estimateSize()); // 5
// Process each half independently (could be on separate threads):
prefix.forEachRemaining(n -> System.out.print(n + " ")); // 1 2 3 4 5
System.out.println();
original.forEachRemaining(n -> System.out.print(n + " ")); // 6 7 8 9 10
// ── Characteristics bitmask ───────────────────────────────────────────
Spliterator<String> arraySplit = Arrays.asList("x", "y", "z").spliterator();
int chars = arraySplit.characteristics();
System.out.println(Integer.toBinaryString(chars)); // shows set flags
System.out.println(arraySplit.hasCharacteristics(Spliterator.SIZED)); // true
System.out.println(arraySplit.hasCharacteristics(Spliterator.ORDERED)); // true
System.out.println(arraySplit.hasCharacteristics(Spliterator.DISTINCT)); // false
// HashSet: DISTINCT but not ORDERED
Spliterator<String> setSplit = new HashSet<>(List.of("a","b","c")).spliterator();
System.out.println(setSplit.hasCharacteristics(Spliterator.DISTINCT)); // true
System.out.println(setSplit.hasCharacteristics(Spliterator.ORDERED)); // false
// ── Spliterators utility factory methods ──────────────────────────────
// Wrap an Iterator into a Spliterator (characteristics must be specified manually):
Iterator<String> iter = List.of("p", "q", "r").iterator();
Spliterator<String> fromIter = Spliterators.spliteratorUnknownSize(
iter,
Spliterator.ORDERED // we know it's ordered, nothing else guaranteed
);
// Create a Stream from a Spliterator:
Stream<String> stream = StreamSupport.stream(fromIter, false); // false = sequential
stream.forEach(System.out::println); // p q rCharacteristic Flags — The Eight Constants
// ── Examining characteristics of standard collections ─────────────────
import java.util.Spliterator;
// ArrayList: ORDERED, SIZED, SUBSIZED
List<Integer> arrayList = new ArrayList<>(List.of(1, 2, 3));
Spliterator<Integer> alSplit = arrayList.spliterator();
printCharacteristics("ArrayList", alSplit);
// HashSet: DISTINCT, SIZED
Set<Integer> hashSet = new HashSet<>(List.of(1, 2, 3));
Spliterator<Integer> hsSplit = hashSet.spliterator();
printCharacteristics("HashSet", hsSplit);
// TreeSet: DISTINCT, ORDERED, SIZED, SORTED
Set<Integer> treeSet = new TreeSet<>(List.of(3, 1, 2));
Spliterator<Integer> tsSplit = treeSet.spliterator();
printCharacteristics("TreeSet", tsSplit);
System.out.println("Comparator: " + tsSplit.getComparator()); // null = natural order
// Arrays.asList: ORDERED, SIZED, SUBSIZED
Spliterator<String> arrSplit = Arrays.asList("a","b","c").spliterator();
printCharacteristics("Arrays.asList", arrSplit);
// ConcurrentHashMap.keySet(): DISTINCT, CONCURRENT
Map<String,Integer> chm = new ConcurrentHashMap<>();
chm.put("x", 1); chm.put("y", 2);
Spliterator<String> chmSplit = chm.keySet().spliterator();
printCharacteristics("ConcurrentHashMap.keySet", chmSplit);
// ── Utility method ────────────────────────────────────────────────────
static void printCharacteristics(String name, Spliterator<?> sp) {
System.out.printf("%s:%n", name);
System.out.printf(" SIZED: %b%n", sp.hasCharacteristics(Spliterator.SIZED));
System.out.printf(" SUBSIZED: %b%n", sp.hasCharacteristics(Spliterator.SUBSIZED));
System.out.printf(" ORDERED: %b%n", sp.hasCharacteristics(Spliterator.ORDERED));
System.out.printf(" SORTED: %b%n", sp.hasCharacteristics(Spliterator.SORTED));
System.out.printf(" DISTINCT: %b%n", sp.hasCharacteristics(Spliterator.DISTINCT));
System.out.printf(" NONNULL: %b%n", sp.hasCharacteristics(Spliterator.NONNULL));
System.out.printf(" IMMUTABLE: %b%n", sp.hasCharacteristics(Spliterator.IMMUTABLE));
System.out.printf(" CONCURRENT: %b%n", sp.hasCharacteristics(Spliterator.CONCURRENT));
}
// ── How the Stream pipeline uses characteristics ──────────────────────
// SIZED → pre-allocate result array without resizing
int[] result = IntStream.range(0, 10).toArray(); // knows size = 10 upfront
// SORTED → skip sort() when source is already sorted
TreeSet<Integer> sorted = new TreeSet<>(List.of(5,3,1,4,2));
List<Integer> sortedResult = sorted.stream()
.sorted() // no-op: SORTED already set
.collect(Collectors.toList());
// DISTINCT → skip distinct() when source already has unique elements
Set<String> distinct = new HashSet<>(List.of("a","b","c"));
long count = distinct.stream()
.distinct() // no-op: DISTINCT already set
.count();Custom Spliterator Implementation
// ── Custom Spliterator for a range of integers ───────────────────────
// Demonstrates: SIZED + SUBSIZED + ORDERED + IMMUTABLE + NONNULL
public class RangeSpliterator implements Spliterator.OfInt {
private int current;
private final int end;
public RangeSpliterator(int start, int end) {
this.current = start;
this.end = end;
}
@Override
public boolean tryAdvance(IntConsumer action) {
if (current < end) {
action.accept(current++);
return true;
}
return false;
}
@Override
public OfInt trySplit() {
int mid = (current + end) >>> 1; // midpoint, avoids overflow
if (mid <= current) return null; // too small to split
int lo = current;
current = mid; // this Spliterator now covers [mid, end)
return new RangeSpliterator(lo, mid); // prefix covers [lo, mid)
}
@Override
public long estimateSize() {
return (long)(end - current);
}
@Override
public int characteristics() {
return SIZED | SUBSIZED | ORDERED | IMMUTABLE | NONNULL;
}
}
// Use in a sequential stream:
Spliterator.OfInt sp = new RangeSpliterator(0, 10);
StreamSupport.intStream(sp, false)
.forEach(n -> System.out.print(n + " ")); // 0 1 2 3 4 5 6 7 8 9
// Use in a parallel stream:
Spliterator.OfInt psp = new RangeSpliterator(0, 1_000_000);
long sum = StreamSupport.intStream(psp, true) // true = parallel
.asLongStream()
.sum();
System.out.println(sum); // 499999500000
// ── Custom Spliterator wrapping a binary tree (pre-order) ─────────────
// Uses AbstractSpliterator for built-in batch splitting:
class TreeNode<T> {
T value;
TreeNode<T> left, right;
TreeNode(T v, TreeNode<T> l, TreeNode<T> r) { value=v; left=l; right=r; }
}
public class TreeSpliterator<T> extends Spliterators.AbstractSpliterator<T> {
private final Deque<TreeNode<T>> stack = new ArrayDeque<>();
public TreeSpliterator(TreeNode<T> root) {
super(Long.MAX_VALUE, ORDERED); // size unknown → MAX_VALUE; ordered = pre-order
if (root != null) stack.push(root);
}
@Override
public boolean tryAdvance(Consumer<? super T> action) {
if (stack.isEmpty()) return false;
TreeNode<T> node = stack.pop();
action.accept(node.value);
if (node.right != null) stack.push(node.right);
if (node.left != null) stack.push(node.left);
return true;
}
// trySplit() inherited from AbstractSpliterator — batches elements automatically
}
// Build a small tree and stream it:
// 1
// / \
// 2 3
// / \
// 4 5
TreeNode<Integer> tree = new TreeNode<>(1,
new TreeNode<>(2,
new TreeNode<>(4, null, null),
new TreeNode<>(5, null, null)),
new TreeNode<>(3, null, null));
StreamSupport.stream(new TreeSpliterator<>(tree), false)
.forEach(n -> System.out.print(n + " ")); // 1 2 4 5 3 (pre-order)