ByteScrollGet the app
☰ Topics
iterator-types4 / 200‹›
JAVA / COLLECTIONS2 minute read

Iterator vs ListIterator vs Spliterator

Medium

Iterator walks any collection forward and can remove. ListIterator is List-only: it moves both ways, knows indexes, and can set or add. Spliterator (Java 8) can split itself in two, which is what lets streams hand chunks of a collection to parallel workers.

How it works

  1. Iterator has hasNext(), next(), optional remove() and forEachRemaining(). Every Collection provides one.
  2. ListIterator extends Iterator for lists. It adds hasPrevious(), previous(), nextIndex(), previousIndex(), set(e) to replace the last returned element and add(e) to insert at the cursor. list.listIterator(i) starts at any index.
  3. Spliterator ("splittable iterator") is built for streams:
    • tryAdvance(action) processes one element if there is one.
    • trySplit() hands off roughly half of the remaining elements as a new Spliterator, or returns null if it can't split.
    • estimateSize() and characteristics() (ORDERED, SIZED, SUBSIZED, SORTED, DISTINCT, NONNULL, IMMUTABLE, CONCURRENT) let the stream pipeline plan its work.
  4. Parallel streams call trySplit() repeatedly to build a tree of chunks, then process the leaves on the common ForkJoinPool.
trySplitkeepstrySplitkeepstrySplitkeepsSpliteratorover 0..70..34..70..12..34..56..7worker 1worker 2worker 3worker 4
trySplitkeepstrySplitkeepstrySplitkeepsSpliteratorover 0..70..34..70..12..34..56..7worker 1worker 2worker 3worker 4

Example

Example.javaJava
List<String> words = new ArrayList<>(List.of("tea", "cofee", "juice"));

// ListIterator: fix a typo in place and insert after it
ListIterator<String> li = words.listIterator();
while (li.hasNext()) {
    if (li.next().equals("cofee")) {
        li.set("coffee");
        li.add("cocoa");     // inserted after "coffee"
    }
}
// [tea, coffee, cocoa, juice]

// Walk backwards from the end
for (ListIterator<String> back = words.listIterator(words.size()); back.hasPrevious(); ) {
    System.out.print(back.previous() + " ");
}

// Spliterator: split once and see both halves
Spliterator<String> right = words.spliterator();
Spliterator<String> left = right.trySplit();
left.forEachRemaining(w -> System.out.println("L " + w));  // tea, coffee
right.forEachRemaining(w -> System.out.println("R " + w)); // cocoa, juice

Edge cases

  • ListIterator.set() and remove() throw IllegalStateException if called before next()/previous() or right after add().
  • ArrayList's spliterator binds late (it reads the list's size at first use, not at creation) and is fail-fast like its iterator.
  • A spliterator that reports SIZED | SUBSIZED lets a stream size arrays and split evenly; a LinkedList splits poorly because finding the middle means walking it.
  • To stream your own source, wrap a spliterator: StreamSupport.stream(spl, parallel).

Common mistakes

  • Calling remove() on the list while holding an Iterator. Use the iterator's remove().
  • Saying Spliterator is "just a parallel iterator". It also works sequentially; splitting is optional.
  • Forgetting ListIterator's cursor sits between elements, so nextIndex() and previousIndex() differ by one.

Likely follow-up

"Why does a parallel stream over a LinkedList often run slower than sequential?" Its spliterator can't jump to the middle, so splits are uneven and expensive. An ArrayList or array splits in O(1) by index.

Get every deep dive in the app

Coming soon to the App StoreComing soon to Google Play