🌊 Streams API · Intermediate

sorted, distinct, limit, skip in Java

Stateful intermediate operations.

🧩 The mysteryStream.iterate(1, n -> n + 1).sorted().limit(3) — you'd expect [1, 2, 3]. Instead your program hangs forever. Why can't limit save it?

Operations with a memory

filter and map judge each element alone. **distinct and sorted are stateful**: they must remember what they've seen. sorted is like a teacher who can't announce the top student until every exam is handed in — it must see all elements before emitting the first.

distinct and sorted

**distinct() keeps the first occurrence of each element, judging duplicates by equals/hashCode. sorted() uses natural order; sorted(comparator)** uses your rule.

Stream.of("pear", "fig", "apple")
    .sorted(Comparator.comparing(
        String::length))
    .toList(); // [fig, pear, apple]
🔮 Predict it

distinct alone

What does this print?

List<Integer> r = Stream.of(4, 1, 4, 2, 1)
    .distinct()
    .toList();
System.out.println(r);
  1. [1, 2, 4]
  2. [4, 1, 2]
  3. [4, 1, 4, 2, 1]
Show the answer

[4, 1, 2] — distinct drops repeats but keeps encounter order. It does not sort; chain .sorted() for [1, 2, 4].

skip and limit: paging

**skip(n) drops the first n elements; limit(n) keeps at most** n. Together they fetch one page: page p of size s is skip(p * s).limit(s).

🔮 Predict it

Page three

What does this print?

var r = Stream.iterate(1, n -> n + 1)
    .skip(10)
    .limit(3)
    .toList();
System.out.println(r);
  1. [10, 11, 12]
  2. [11, 12, 13]
  3. [1, 2, 3]
Show the answer

[11, 12, 13] — skip throws away 1 to 10, then limit keeps the next three. limit also makes the infinite source safe.

⚠️ The trap

sorted on an infinite stream

sorted waits for every element before emitting anything. The source never ends, so limit(3) never receives a single element — the program runs forever (or runs out of memory). Put **limit before sorted**.

Stream.iterate(1, n -> n + 1)
    .sorted()     // waits forever
    .limit(3)
    .toList();
💼 In the real world

Paging and leaderboards

skip/limit power "page 3 of results" in small in-memory lists, and sorted(...).limit(10) builds top-10 leaderboards. On big datasets teams push sorting and paging into the database instead, because a stream must load everything to sort it.

Key takeaways

  1. sorted() uses natural order; sorted(cmp) uses a Comparator
  2. distinct() removes duplicates using equals/hashCode
  3. skip(n) drops the first n; limit(n) keeps at most n
  4. sorted on an infinite stream never finishes
🤯 Did you know?

sorted() on objects that aren't Comparable compiles fine but throws a ClassCastException at runtime when the terminal op runs.

Practice questions

What does this print?

System.out.println(
    Stream.of(5, 3, 5, 1, 3)
        .distinct()
        .sorted()
        .toList());
  1. [5, 3, 1]
  2. [1, 3, 5]
  3. [1, 3, 3, 5, 5]
  4. [5, 5, 3, 3, 1]
Check your answer

[1, 3, 5]. distinct removes the repeated 5 and 3, leaving 5, 3, 1; sorted then puts them in natural ascending order.

What does this print?

List<Integer> page = IntStream.rangeClosed(1, 10)
    .boxed()
    .skip(3)
    .limit(4)
    .toList();
System.out.println(page);
  1. [3, 4, 5, 6]
  2. [4, 5, 6, 7, 8]
  3. [4, 5, 6, 7]
  4. [1, 2, 3, 4]
Check your answer

[4, 5, 6, 7]. skip(3) drops 1, 2 and 3; limit(4) then keeps the next four: 4 to 7. skip + limit is the classic way to fetch one page of results.

Next: the end of the belt. What does allMatch say about an empty stream — true or false?