Що роблять операції distinct(), sorted(), limit(), skip()?
Усі чотири операції — проміжні (intermediate), але вони відрізняються від filter та map:
🟢 Junior Level
Усі чотири операції — проміжні (intermediate), але вони відрізняються від filter та map:
distinct() — видаляє дублікати:
List.of(1, 2, 2, 3, 3, 3).stream().distinct().collect(toList());
// [1, 2, 3]
sorted() — сортує елементи:
List.of(3, 1, 2).stream().sorted().collect(toList());
// [1, 2, 3]
// sorted() у parallelStream: спочатку всі елементи збираються, потім сортуються, // потім розподіляються по воркерах. overhead на merge може перевищити вигоду.
limit(n) — бере перші n елементів:
List.of(1, 2, 3, 4, 5).stream().limit(3).collect(toList());
// [1, 2, 3]
skip(n) — пропускає перші n елементів:
List.of(1, 2, 3, 4, 5).stream().skip(2).collect(toList());
// [3, 4, 5]
🟡 Middle Level
Stateful операції (зі станом)
Stateful операції — потребують знання ВСІХ елементів пайплайну, а не поточного. distinct() повинен побачити всі елементи, щоб знайти унікальні. sorted() повинен відсортувати весь набір.
distinct() та sorted() — stateful. Вони вимагають буферизації:
distinct()— створює внутрішнійHashSetдля відстеження унікальних елементівsorted()— це бар’єр у конвеєрі. Стрим збирає ВСІ елементи, сортує, потім передає далі
Memory: Обидві споживають пам’ять O(N) — можуть призвести до росту купи та GC-пауз на великих даних.
Short-circuit операції
limit() та skip() — операції короткого замикання:
limit(n)— сигналізує про припинення роботи через прапорець скасуванняskip(n)— має внутрішній лічильник, “поглинає” елементи
Оптимізація порядку
// ПОГАНО — сортуємо мільйон, потім беремо 10
stream.sorted().limit(10)...
// ДОБРЕ — спочатку фільтруємо, зменшуючи N
stream.filter(relevant).sorted().limit(10)...
🔴 Senior Level
Коли НЕ використовувати
distinct()— якщо дані вже унікальні (HashSet на вході) — зайва перевіркаsorted()— якщо порядок не важливий (використовуйте findAny замість findFirst)limit(n)після sorted() — сортування всього набору заради перших n (використовуйте PriorityQueue)skip(n)для пагінації на великих даних — O(n) пропуск, краще keyset pagination
Проблеми паралелізму
У parallelStream ці операції стають вузьким місцем:
limit()таskip(): Вимагають жорсткої синхронізації між потокамиdistinct(): Об’єднання хеш-сетів з різних потоків дорогеsorted(): Паралельне сортування ефективне лише на дуже великих масивах
Edge Cases
distinct()на мутабельних об’єктах: Якщо змінили поле об’єкта після проходженняdistinct— порушення контракту унікальності- Стабільність
sorted(): Стрими гарантують стабільне сортування (збереження порядку рівних елементів), якщо джерело впорядковане
Діагностика
jmap -histo: Покаже роздуттяHashSetабоObject[]приdistinct/sortedна великих даних- Infinite stream check: Якщо нескінченний стрим (
Stream.iterate) завис — перевірте, чи стоїтьlimit()до або після важких фільтрів
🎯 Шпаргалка для інтерв’ю
Обов’язково знати:
distinct()таsorted()— stateful операції, вимагають буферизації всіх елементів у пам’яті (O(N))distinct()використовує внутрішній HashSet для відстеження унікальностіsorted()— бар’єр у конвеєрі: збирає ВСІ елементи, сортує, потім передає даліlimit()таskip()— short-circuit операції, не вимагають буферизації- Порядок важливий:
filter().sorted().limit()ефективніше заsorted().limit() limit()післяsorted()— сортування всього набору заради перших n (використовуйте PriorityQueue)skip()для пагінації на великих даних — O(n), краще keyset pagination- У parallelStream stateful операції стають вузьким місцем через синхронізацію
Часті уточнюючі запитання:
- Чим stateful операції відрізняються від stateless? — Stateless (filter, map) обробляють кожен елемент незалежно; stateful (distinct, sorted) повинні бачити всі елементи цілком.
- Чому sorted() — бар’єр? — Стрим не може відсортувати елементи, поки не збере їх усі — це блокує конвеєр до повного збору даних.
- Що гірше для продуктивності — distinct чи sorted? — sorted() важче, оскільки вимагає повної буферизації та самого сортування; distinct() використовує HashSet з O(1) перевіркою.
- Чому limit() після sorted() — антипатерн? — Ви сортуєте весь набір даних, а потім берете перші n — краще відфільтрувати та обмежити до сортування.
Червоні прапорці (НЕ говорити):
- «sorted() обробляє елементи по одному» — неправда, це бар’єр, потрібні всі елементи
- «distinct() не споживає додаткову пам’ять» — неправда, створює внутрішній HashSet
- «limit() після sorted() — оптимально» — неправда, сортування всього набору заради n елементів надлишкове
- «parallelStream завжди прискорює sorted()» — неправда, overhead на merge може перевищити вигоду
Пов’язані теми:
- [[21. Що таке lazy evaluation в Stream]]
- [[24. Як працює коротке замикання (short-circuiting) в Stream]]
- [[26. Що роблять операції findFirst() та findAny()]]
- [[27. Як зібрати Stream в Map]]