Stream API: обзор и основные методы.



Stream API - удобный инструмент обработки данных, который появился в java 8. Два принципа, на которых строится Stream API - composition & single responsibility. Действия собираются из простых независимых друг от друга блоков без сайд-эффектов. Этим достигается удобство чтения и поддержки:

Стандартные названия.

Нет локальных переменных.

Линейная последовательность действий.



Стрим состоит из 3 частей:

1️⃣ Источник данных.

2️⃣ Преобразования.

3️⃣ Конечная операция.



Основные способы создать стрим:

🔸 Из готовой коллекции

🔸 Из набора элементов

🔸 Из массива

🔸 Задать первый элемент и правило вывода последующего, получится бесконечный стрим: Stream.iterate(T, BinaryOperator)

🔸 Задать первый элемент, правило вывода последующего и условие остановки: Stream.iterate(T, Predicate, BinaryOperator) (java 9)

🔸 Генерировать независимые друг от друга элементы: Stream.generate(Supplier)

🔸 Из диапазона с включением граничных значений: IntStream.range(..)

🔸 Диапазон без граничных значений: IntStream.rangeClosed(..)

🔸 Из строк: BufferedReader.lines()

🔸 Из символов строки: CharSequence.chars()



Названия большинства методов преобразований говорят сами за себя: filter, distinct, limit, sorted. Есть менее понятные:

🔹 map: применить функцию к каждому элементу.

🔹 flatmap: положить элементы из "списка списков" в единый стрим. Также с его помощью можно уменьшить размерность массива.

🔹 takeWhile(Predicate): брать элементы из стрима, пока выполняется условие. Метод доступен c java 9.

🔹 dropWhile(Predicate): пропускать элементы, пока условие не нарушится. Метод доступен c java 9.

🔹 peek(Consumer): сделать что-то с каждым элементом стрима, не меняя исходный стрим. Часто используется при дебаге, например, вывести в консоль текущие элементы.



Вычисления выполняются не сразу, а только когда вызывается конечная операция. Конечная операция описывает желаемый результат. Результатом может быть:

◾️ Структура данных: toArray, collect.

◾️ Результат поиска: anyMatch, allMatch, nonMatch, findFirst, findAny.

◾️ Результат агрегации: min, max, count, reduce.

◾️ Сайд-эффект: forEach, forEachOrdered.



Стрим - не структура хранения данных. Он обходит источник данных (или сам его генерирует) и сохраняет промежуточные результаты. По этой причине нельзя копировать стримы и исполнять их несколько раз - пришлось бы контролировать этапы исполнения, консистентность данных и добавить синхронизацию для работы в многопоточной среде. При попытке переиспользования стрима выбрасывается IllegalStateException.

Исходные данные не меняются.

Стрим нельзя переиспользовать.

Пока не вызвана конечная операция, стрим можно менять как угодно.



Пример из опроса выше вернёт 1.

list.remove(0) удаляет элемент с индексом 0, поэтому на момент старта вычислений stream.count() в источнике данных остаётся всего один элемент.



Если Вас раздражает, что метод list.remove принимает не сам элемент, а индекс, то некоторые методы Stream API Вас тоже разочаруют. Например, такой код компилируется:

Stream.of(-1, 0, 1).max(Math::max).get();

Но в результате получается -1, потому что входной параметр метода max - Comparator. Math.max по сигнатуре на него похож, поэтому получается неверный результат😒