Definition
- SIZED – exact element count is known; the pipeline can pre-allocate result containers and shortcut
count(). - ORDERED – elements have a meaningful encounter order that must be preserved.
- SORTED – elements are already sorted;
sorted()becomes a no-op. - DISTINCT – all elements are unique;
distinct()becomes a no-op. - NONNULL – no element is null.
- IMMUTABLE – the source will not be modified during traversal.
- CONCURRENT – the source supports safe concurrent modification.
The conveyor belt has labels: SORTED by amount, exactly 100 items (SIZED), all unique (DISTINCT). If the belt is labeled SORTED and you ask to sort it, skip it. If labeled SIZED, the packing machine pre-cuts a box of exactly 100 slots. Labels let the factory skip unnecessary work.
Understanding the Concepts
What are Spliterator characteristics?
A Spliterator is the engine under every stream. It traverses elements (tryAdvance) and splits them for parallelism (trySplit). Its CHARACTERISTICS are bit flags that describe the source's properties.
How do characteristics enable optimization?
| Characteristic | Optimization |
|---|---|
| SIZED | Pre-allocate result arrays/lists. count() returns size without traversal. |
| ORDERED | Pipeline preserves encounter order. Absence allows unordered parallel. |
| SORTED | sorted() becomes a no-op if the comparator matches. |
| DISTINCT | distinct() becomes a no-op. |
| NONNULL | Null checks can be skipped. |
| IMMUTABLE | No ConcurrentModificationException risk. |
Which collections have which characteristics?
import java.util.*;
import java.util.stream.Stream;
// ArrayList: ORDERED, SIZED → great for parallel (splits in O(1))
List<String> list = new ArrayList<>(List.of("SAV", "CUR", "FD"));
list.stream().spliterator().characteristics();
// ORDERED | SIZED | SUBSIZED
// HashSet: DISTINCT, SIZED → not ORDERED
Set<String> set = new HashSet<>(Set.of("SAV", "CUR", "FD"));
// DISTINCT | SIZED
// TreeSet: DISTINCT, SORTED, ORDERED, SIZED
TreeSet<String> treeSet = new TreeSet<>(Set.of("SAV", "CUR", "FD"));
// DISTINCT | SORTED | ORDERED | SIZED
// treeSet.stream().sorted() → no-op (already SORTED)
How does SIZED affect count()?
// ArrayList is SIZED: count() returns list.size() without processing elements
long count = list.stream()
.peek(s -> System.out.println("peek: " + s)) // may NEVER execute!
.count();
// peek may be skipped because count uses the SIZED characteristic
How does SORTED affect sorted()?
// TreeSet is already SORTED: sorted() is a no-op
List<String> result = treeSet.stream()
.sorted() // no-op: source is already SORTED
.toList();
// No sorting work done. Result is [CUR, FD, SAV] (natural order from TreeSet).
How does DISTINCT affect distinct()?
// HashSet is DISTINCT: distinct() is a no-op
List<String> result = set.stream()
.distinct() // no-op: source is already DISTINCT
.toList();
// No deduplication work done.
How do characteristics affect parallel performance?
- **ArrayList** – SIZED + splits in O(1) via array index → excellent for parallel. - **LinkedList** – not efficiently splittable → poor for parallel. - **HashSet** – SIZED + bucket-based splitting → decent for parallel. - **Streams without SIZED** – cannot pre-allocate → more GC pressure.
What should a senior engineer know?
- Characteristics are propagated through the pipeline. filter() removes SIZED because the result count is unknown. sorted() adds SORTED.
- Understanding characteristics explains why
treeSet.stream().sorted()is instant whilearrayList.stream().sorted()buffers all elements. - Custom Spliterators should report accurate characteristics for optimal pipeline behavior.
- The
unordered()intermediate operation removes ORDERED, allowing the pipeline to skip order-preservation in parallel.
What are common interview follow-up questions?
- How does filter affect SIZED? (Removes it. The pipeline cannot know how many elements pass the filter.)
- Can you add characteristics to a stream? (sorted() adds SORTED. distinct() adds DISTINCT. unordered() removes ORDERED.)
- Why does count() sometimes skip peek? (If the source is SIZED and no operations change the count, count() reads the size directly.)
Visual Overview
Spliterator Characteristics by Collection: ┌─────────────┬─────────┬─────────┬──────────┬──────────┐ │ Collection │ ORDERED │ SIZED │ SORTED │ DISTINCT │ ├─────────────┼─────────┼─────────┼──────────┼──────────┤ │ ArrayList │ Yes │ Yes │ No │ No │ │ LinkedList │ Yes │ Yes │ No │ No │ │ HashSet │ No │ Yes │ No │ Yes │ │ TreeSet │ Yes │ Yes │ Yes │ Yes │ │ LinkedHSet │ Yes │ Yes │ No │ Yes │ └─────────────┴─────────┴─────────┴──────────┴──────────┘ Optimization examples: TreeSet.stream().sorted() → no-op (SORTED) HashSet.stream().distinct() → no-op (DISTINCT) ArrayList.stream().count() → list.size() (SIZED)
Key Takeaway
Spliterator characteristics are optimization hints that the stream pipeline reads before execution. SIZED means the element count is known, enabling pre-allocated result containers and count() shortcutting without element traversal. SORTED means elements are already sorted, making sorted() a no-op. DISTINCT means elements are unique, making distinct() a no-op. ORDERED means encounter order must be preserved.
These flags explain several surprising stream behaviors. Why does treeSet.stream().sorted() run instantly? Because the source is SORTED and the pipeline skips sorting. Why does list.stream().peek(log).count() sometimes not print anything? Because the source is SIZED and count() reads the size directly.
Choose the source collection based on the stream workload. Array-backed sources (ArrayList) give SIZED and split efficiently for parallel processing. TreeSet gives SORTED + DISTINCT. HashSet gives DISTINCT but not ORDERED. Understanding characteristics is the key to predicting stream performance.
Remember one sentence: Spliterator characteristics like SIZED, SORTED, and DISTINCT let the pipeline skip redundant operations and pre-allocate results.
---