Java8 stream API, часть четвёртая: сортировка

single-stream-recyclingЕсли к примеру многопоточной обработки дописать вывод результатов, мы увидим, что данные после обработки имеют случайный порядок:

Что, если нам нужен не просто список количества фильмов в каждом году, а ещё и отсортированный по годам? Можно, конечно, позвать Collections.sort()  с какой-то своей реализацией Comparable. Но для Map нет вызова Collections.sort()! Можно использовать какую-нибудь реализацию SortedMap  и отсортировать результаты вручную, но это требует лишнего кода и не менее лишнего копирования данных. Правильный ответ: можно использовать сортированные Streams:

Метод sorted() сортирует поток, используя естественный порядок сравнения его элементов. Для IntStream, из примера выше, другой порядок сортировки задать нельзя. Для обычного потока существует второй метод sorted(), принимающий либо экземпляр Comparable, либо соответствующее ему лямбда-выражение.

Надо отметить, что sorted() является методом с состоянием. Все методы из предыдущих статей о Stream API не имели своего состояния, зависящего от предыдущих вызовов и могли быть вызваны независимо для каждого элемента потока. Метод с состоянием, такой как sorted(), может не иметь возможности вернуть результат до того момента, пока не обработает весь поток. Из этого следует, что такие методы не могут работать с бесконечными потоками. Так же из этого следует, что в ходе (параллельной) обработки могут быть созданы дополнительные копии данных или данные могут быть обработаны в несколько проходов.

Использование сортированных потоков особенно удобно с некоторыми терминальными операциями. Например, чтобы узнать год, в котором было снято наибольшее число фильмов, отсортируем результат по убыванию и возьмём первый элемент:

findFirst()  возвращает первый элемент потока. Если быть точным, то findFirst() возвращает Optional, который может быть будет содержать первый элемент потока, если такой будет существовать. Использовать findFirst() в основном имеет смысл с сортированными потоками, так как в ином случае гарантировать порядок мы не можем. Для несортированных потоков выгоднее использовать findAny() , вовращающей любой элемент потока. Этот метод может быть более эффективным, так как не всегда первый элемент потока будет первым доступным, а для несортированных потоков разницы, по большому счёту, нет. Так же как findFirst(), findAny() возвращает на самом деле Optional, который может либо содержать данные, либо быть пустым, если поток так же пуст.

Ещё один метод, limit(), позволяет выбрать из потока первые несколько элементов:

Метод skip() позволяет пропустить некоторое количество элементов перед дальнейшей обработкой. Вторая пятёрка годов по колиеству фильмов делается с его помощью очень просто:

Три метода, findFirst(), findAny(), limit() имеют ещё одно свойство — они способны завершить бесконечный поток. Таким же свойством обладает и рассмотренный ранее метод anyMatch().

Код примера доступен на github. Для исполнения требуется скачать IMDB movies.list и положить его в src/resources.