Как оптимально применять distinct и filter в Stream при большом количестве элементов и повторений в Java?
Что проверяют
Понимаете ли вы, что filter работает без состояния, а distinct хранит все встреченные уникальные элементы, и как из этого следует порядок операций. Следом спрашивают про параллельные стримы и про уникальность по полю, а не по объекту целиком.
Как отвечать
Суть в стоимости операций:
filter: stateless, обработал элемент и забыл, памяти не требует;distinct: stateful, в последовательном стриме держитHashSetувиденных элементов, память растёт с числом уникальных. Опирается наequals()иhashCode(), уrecordони корректны из коробки.
Отсюда правило порядка. Дешёвый и селективный filter ставят перед distinct, множество получится меньше. Если предикат дорогой (запрос, парсинг), а дубликатов очень много, выгоднее сначала distinct, чтобы не звать предикат на каждом повторе. Результат одинаков, пока предикат зависит только от значения элемента.
Что ещё стоит сказать:
- параллельный стрим:
distinctна упорядоченном источнике обязан сохранить порядок встречи, и это дорогой барьер. Если порядок не нужен,.unordered()заметно его облегчает; - уникальность по ключу:
distinctByв стандартной библиотеке нет. Варианты:Collectors.toMap(key, identity(), (a, b) -> a, LinkedHashMap::new)или свойGatherer, этот API стал стабильным в Java 24. Приёмfilter(seen::add)с внешнимHashSetгодится только для последовательного стрима; - примитивы:
IntStream.distinct()внутри боксит значения. Для ограниченного диапазона быстрее пройти черезBitSet; - очень большие объёмы: если уникальные значения не помещаются в память, дедупликацию переносят в базу (
SELECT DISTINCT,GROUP BY) или делают внешней сортировкой.
Где спотыкаются
Советуют collect(toSet()).stream() как «быстрый distinct». Это тот же хэш-сет плюс лишний проход, и порядок элементов теряется.
Включают parallel() в расчёте на ускорение и не учитывают, что упорядоченный distinct съедает выигрыш, а иногда работает медленнее последовательного.
Дедуплицируют объекты класса без переопределённых equals() и hashCode(). Сравнение тогда идёт по ссылке, и равные по содержимому элементы остаются в результате.