Что делают операции 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()» — неверно, оверхед на merge может превысить выгоду
Связанные темы:
- [[21. Что такое lazy evaluation в Stream]]
- [[24. Как работает короткое замыкание (short-circuiting) в Stream]]
- [[26. Что делают операции findFirst() и findAny()]]
- [[27. Как собрать Stream в Map]]