Stream java что это

Полное руководство по Java Stream API

Java Stream API был добавлен в Java 8 вместе с несколькими другими функциями функционального программирования. В этом руководстве по Java Stream разберем, как работают эти функциональные потоки и как ими пользоваться.

API Java Stream не связан с Java InputStream и Java OutputStream Java IO. InputStream и OutputStream связаны с потоками байтов. Предназначен для обработки потоков объектов, а не байтов.

Java Stream – это компонент, способный выполнять внутреннюю итерацию своих элементов, то есть он может выполнять итерацию своих элементов сам.

Напротив, когда используются итерационные функции, мы должны сами реализовать итерацию элементов.

Потоковая обработка

Можно прикрепить слушателей к потоку. Эти слушатели вызываются, когда Stream выполняет внутреннюю итерацию элементов. Слушатели вызываются один раз для каждого элемента в потоке.

Каждый слушатель получает возможность обрабатывать каждый элемент в потоке. Это называется потоковой обработкой.

Слушатели потока формируют цепочку. Первый слушатель в цепочке может обработать элемент в потоке, а затем вернуть новый элемент для обработки следующим слушателем в цепочке. Слушатель может возвращать тот же элемент или новый, в зависимости от назначения.

Stream java что это. lazy placeholder. Stream java что это фото. Stream java что это-lazy placeholder. картинка Stream java что это. картинка lazy placeholder

Получение потока

Есть много способов получить поток Java. Один из самых распространенных способов получить поток – из Java Collection.

В этом примере сначала создали список Java, а затем добавили три строки. В итоге, в примере вызывается метод stream() для получения экземпляра потока.

Терминальные и не-терминальные операции

Интерфейс Stream имеет выбор терминальных и не-терминальных операций.

Не-терминальная потоковая операция – это операция, которая добавляет слушателя в поток, не делая ничего другого.

Операция терминального потока – это операция, которая запускает внутреннюю итерацию элементов, вызывает всех слушателей и возвращает результат.

Вызов метода map() является не-терминальной операцией. Он просто преобразует каждый элемент в нижний регистр. Вызов метода count() является терминальной операцией. Этот вызов запускает внутреннюю итерацию, в результате чего каждый элемент преобразуется в нижний регистр.

Преобразование элементов в нижний регистр фактически не влияет на количество элементов.

Не-терминальные операции

Нетерминальные операции потока Java Stream API являются операциями, которые преобразовывают или фильтруют элементы в потоке. Когда добавляется не-терминальная операция в поток, в итоге мы получаем новый поток.

Этот вызов возвращает новый экземпляр Stream, представляющий исходный поток строк с применяемой операцией.

Можно добавить только одну операцию в данный экземпляр. Если необходимо объединить несколько операций, следующих друг за другом, потребуется применить вторую операцию к операции потока, полученной в результате первой.

Обратим внимание, как второй вызов map() вызывается в потоке, возвращаемом первым вызовом map().

Многие нетерминальные операции Stream могут принимать Java Lambda Expression в качестве параметра. Это лямбда-выражение реализует Java functional interface, который подходит для данной не-терминальной операции.

Например, интерфейс Function или Predicate. Параметр метода не-терминальной операции обычно является функциональным интерфейсом, поэтому его также можно реализовать с помощью Java lambda expression.

filter()

filter() можно использовать для фильтрации элементов. Метод фильтра принимает Predicate, который вызывается для каждого элемента в потоке.

Если элемент должен быть включен в результирующий поток, Predicate должен вернуть значение “true”. Если элемент не должен быть включен, Predicate должны возвращать значение “false”.

Метод map() преобразует (отображает) элемент в другой объект. Например, если у нас был список строк, он мог бы преобразовать каждую строку в нижний регистр, верхний регистр или в подстроку исходной строки.

flatMap()

Методы Java Stream flatMap() отображают один элемент на несколько элементов. Идея состоит в том, что мы «сплющиваем» каждый элемент из сложной структуры, состоящей из нескольких внутренних элементов, в «плоский» поток, состоящий только из этих внутренних элементов.

Например, представим, что у нас есть объект с вложенными объектами (дочерние объекты). Затем можно отобразить этот объект в «плоский» поток, состоящий из себя плюс его вложенные объекты – или только вложенные объекты.

Можно также отобразить поток списков элементов на сами элементы. Или сопоставить поток строк с потоком слов в этих строках – или с отдельными экземплярами символов в этих строках.

Этот пример flatMap() сначала создает список из 3 строк, содержащих названия книг. Затем получается поток для списка и вызывается flatMap().

flatMap(), вызываемый в потоке, должен возвращать другой поток, представляющий элементы плоского отображения. В приведенном выше примере каждая исходная строка разбивается на слова, превращается в список, а поток получается и возвращается из этого списка.

Этот пример заканчивается вызовом forEach(). Предназначен только для запуска внутренней итерации и, следовательно, операции flat map. Если в цепочке Stream не было вызвано ни одной операции терминала, ничего бы не произошло. Никакого плоского картирования на самом деле не было бы.

distinct()

Метод distinct() это не-терминальная операция, которая возвращает новый поток, который будет содержать только отдельные элементы из исходного потока. Любые дубликаты будут удалены.

В этом примере элемент 1 появляется 2 раза в исходном потоке. Только первое вхождение этого элемента будет включено в поток, возвращаемый Different(). Таким образом, результирующий список (от вызова collect()) будет содержать только один, два и три. Вывод будет:

limit()

Метод limit()может ограничивать количество элементов в потоке числом, данным методу limit() в качестве параметра. Метод limit() возвращает новый поток, который будет максимально содержать заданное количество элементов.

В этом примере сначала создается Stream, затем вызывается limit(), а затем вызывается forEach() с лямбда-выражением, которое выводит элементы в потоке. Только два первых элемента будут напечатаны из-за вызова limit(2).

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

Цель состоит в том, чтобы посмотреть на элементы в потоке, а не преобразовать их. Он не запускает внутреннюю итерацию элементов. Для этого нужно вызвать терминальную операцию.

Терминальные операции

Терминальные операции Java Stream обычно возвращают одно значение. Как только операция терминала вызывается в потоке, начинается итерация потока и любого из связанных потоков. По завершении итерации возвращается результат операции терминала.

Операция терминала обычно не возвращает новый экземпляр. Таким образом, как только вызывается терминальная операция в потоке, цепочка экземпляров Stream из не-терминальной операции заканчивается.

Поскольку count() возвращает long, цепочка нетерминальных операций заканчивается.

anyMatch()

Метод anyMatch() – это терминальная операция, которая принимает один Predicate в качестве параметра, запускает внутреннюю итерацию потока и применяет параметр Predicate к каждому элементу.

Если Predicate возвращает true для любого из элементов, метод anyMatch() возвращает true. Если ни один элемент не соответствует Predicate, anyMatch() вернет false.

В приведенном выше примере вызов вернет true, поскольку первый строковый элемент в потоке начинается с «One».

allMatch()

Метод Java Stream allMatch() является терминальной операцией, которая принимает один Predicate в качестве параметра, запускает внутреннюю итерацию элементов в потоке и применяет параметр Predicate к каждому элементу.

Если Predicate возвращает true для всех элементов в потоке, allMatch() вернет true. Если не все элементы соответствуют Predicate, метод allMatch() возвращает false.

В приведенном выше примере метод allMatch() вернет false, поскольку только одна из строк в Stream начинается с «One».

noneMatch()

noneMatch() является терминальной операцией, которая будет выполнять итерацию элементов в потоке и возвращать true или false в зависимости от того, соответствуют ли элементы в потоке Predicate, переданному noneMatch() в качестве параметра.

Метод noneMatch() вернет значение true, если ни один элемент не соответствует элементу Predicate, и значение false, если один или несколько элементов соответствуют.

Вот пример использования:

collect()

Метод collect() является терминальной операцией, которая запускает внутреннюю итерацию элементов и собирает элементы в потоке в коллекции или объекты какого-либо вида.

Метод collect() принимает в качестве параметра Collector (java.util.stream.Collector). Реализация Collector требует некоторого изучения интерфейса Collector.

К счастью, класс Java java.util.stream.Collectors содержит набор предварительно реализованных действий Collector, которые можно использовать для наиболее распространенных операций.

В приведенном выше примере использовалась реализация Collector, возвращаемая Collectors.toList(). Этот Collector просто собирает все элементы в потоке в стандартный список Java.

count()

Метод подсчета является терминальной операцией, которая подсчитывает элементы. Вот пример подсчета:

Сначала создается список строк, затем получается поток для этого списка, для него добавляется операция flatMap(), а затем заканчивается вызов метода count().

Метод count() запускает итерацию элементов в потоке, в результате чего строковые элементы разбиваются на слова в операции flatMap(), а затем подсчитываются. Окончательный результат, который будет выведен – 14.

findAny()

findAny() может найти отдельный элемент. Здесь все просто и понятно.

Обратим внимание, как метод findAny() возвращает Optional. Поток может быть пустым, поэтому элемент не может быть возвращен. Можно проверить, был ли элемент найден с помощью дополнительного метода isPresent().

FindFirst()

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

Можно проверить, содержит ли возвращаемый Optional элемент через его метод isPresent().

forEach()

forEach() является терминальной операцией, которая запускает внутреннюю итерацию элементов и применяет Consumer (java.util.function.Consumer) к каждому элементу в стриме.

min() является терминальной операцией, которая возвращает наименьший элемент в потоке. Наименьший элемент, определяется реализацией Comparator, которую мы передаем методу min().

Обратим внимание, как метод min() возвращает необязательный параметр, который может содержать или не содержать результат. Если поток пустой, дополнительный метод get() генерирует исключение NoSuchElementException.

max() возвращает самый большой элемент в потоке. Наибольший элемент определяется реализацией Comparator, которую мы передаем методу max().

Возвращает необязательный параметр, который может содержать или не содержать результат. Если поток пустой, дополнительный метод get() будет генерировать исключение NoSuchElementException.

reduce()

reduce() может свести все элементы в потоке к одному элементу. Посмотрите на реализацию:

Обратим внимание на необязательный параметр, возвращаемый методом reduce(). Этот необязательный параметр содержит значение (если оно есть), возвращаемое лямбда-выражением, переданным методу reduce(). Мы получаем значение, вызывая метод Optionalget().

toArray()

Метод Java Stream toArray() является терминальной операцией, которая запускает внутреннюю итерацию элементов в потоке и возвращает массив Object, содержащий все элементы.

Конкатенация потоков в Java

Статический метод concat() может объединять два потока в один. Результатом является новый поток, который содержит все элементы из первого, за которыми следуют все элементы из второго.

Недостатки Java Stream API

По сравнению с другими API потоковой передачи данных, такими как Apache Kafka Streams API, у Java Stream API есть небольшие недостатки. Они не являются очень важными, но их полезно иметь в виду.

Пакетный, но не потоковый

Несмотря на свое название, Java Stream API не является в действительности API потоковой обработки. Терминальные операции возвращают конечный результат итерации по всем элементам в потоке и предоставляют не-терминальные и терминальные операции элементам. Результат операции терминала возвращается после обработки последнего элемента в потоке.

Возврат окончательного результата после обработки последнего элемента потока возможен, только если знать, какой элемент является последним.

Мы никогда не знаем, является ли данный элемент последним или нет. Поэтому невозможно выполнить терминальную операцию. Лучшее, что можно сделать, это собрать временные результаты после обработки данного элемента, но это будет выборка, а не окончательный результат.

Цепь, а не график

Можно добавить только одну не-терминальную операцию в Stream, что приведет к созданию нового объекта. Можно добавить еще одну не-терминальную операцию к результирующему объекту Stream, но не к первому. Результирующая структура нетерминальных экземпляров стрима образует цепочку.

В настоящем API потоковой обработки, корневой поток и слушатели событий обычно могут образовывать график, а не просто цепочку. Несколько слушателей могут прослушивать корневой поток, и каждый слушатель может обрабатывать элементы в потоке по-своему, и в результате могут пересылать преобразованный элемент.

Таким образом, каждый слушатель (не-терминальная операция) обычно может действовать как сам поток, который другие слушатели могут прослушивать результаты. Так устроен Apache Kafka Streams.

Чтобы легко поддерживать операции терминала, должна быть одна, последняя операция, из которой возвращается конечный результат. API обработки потоков на основе графика может вместо этого поддерживать «примерную» операцию, в которой у каждого узла в графике обработки потоков запрашивается любое значение, которое он может содержать внутри (например, сумма), если таковые имеются (чисто преобразовывающие узлы слушателя не будут иметь никакого внутреннего состояния ).

Внутренняя, а не внешняя итерация

Стримы специально разработаны для внутренней итерации элементов в потоке. Итерация начинается, когда терминальная операция вызывается в потоке. Фактически, чтобы терминальные операции могли возвращать результат, терминальная операция должна инициировать итерацию элементов в потоке.

Некоторые API обработки потоков на основе графиков также предназначены для того, чтобы скрыть итерацию элементов от пользователя API (например, Apache Kafka Streams и RxJava).

Однако более предпочтителен проект, в котором каждый узел потока (корневой поток и слушатели) могут иметь элементы, передаваемые им через вызов метода, и этот элемент передается через полный график для обработки.

Такой способ облегчил бы тестирование каждого слушателя на графике, так как вы можно настроить график и пропустить через него элементы, и, наконец, проверить результат.

Средняя оценка / 5. Количество голосов:

Или поделись статьей

Видим, что вы не нашли ответ на свой вопрос.

Источник

Stream API

Stream java что это. 800. Stream java что это фото. Stream java что это-800. картинка Stream java что это. картинка 800

Что такое Stream API?

Stream java что это. 1024. Stream java что это фото. Stream java что это-1024. картинка Stream java что это. картинка 1024

Stream java что это. 1024. Stream java что это фото. Stream java что это-1024. картинка Stream java что это. картинка 1024

Stream java что это. 1024. Stream java что это фото. Stream java что это-1024. картинка Stream java что это. картинка 1024

Stream java что это. 1024. Stream java что это фото. Stream java что это-1024. картинка Stream java что это. картинка 1024

Stream java что это. 1024. Stream java что это фото. Stream java что это-1024. картинка Stream java что это. картинка 1024

Поэтому каждый раз новый:

промежуточных операторов вызванных на одном стриме может быть множество, в то время терминальный оператор только один:

Далее давайте рассмотрим некоторые промежуточные операторы:

Stream java что это. 1024. Stream java что это фото. Stream java что это-1024. картинка Stream java что это. картинка 1024

IntStream.range(0,x) – выдаёт на поток элементов с 0 (включительно) по x (не включительно);

limit(long maxSize) – ограничивает стрим по количеству элементов:

skip(long n) – пропускаем n элементов:

distinct() — проверяет стрим на уникальность элементов(убирает повторы элементов);

dropWhile(Predicate predicate) — пропускает элементы которые удовлетворяют условию (появился в 9 java, Функциональный интерфейс Predicate проверяет соблюдение некоторого условия. Если оно соблюдается, то возвращается значение true. В качестве параметра лямбда-выражение принимает объект типа T:

Stream java что это. 1024. Stream java что это фото. Stream java что это-1024. картинка Stream java что это. картинка 1024

forEach(Consumer action) – аналог for each (Consumer выполняет некоторое действие над объектом типа T, при этом ничего не возвращая);

count() – возвращает количество елементов стрима:

collect(Collector collector) – метод собирает все элементы в список, множество или другую коллекцию, сгруппировывает элементы по какому-нибудь критерию, объединяет всё в строку и т.д.:

reduce(T identity, BinaryOperator accumulator) — преобразовывает все элементы стрима в один объект(посчитать сумму всех элементов, либо найти минимальный элемент), cперва берётся объект identity и первый элемент стрима, применяется функция accumulator и identity становится её результатом. Затем всё продолжается для остальных элементов.

Optional min(Comparator comparator)
Optional max(Comparator comparator) ищет минимальный/максимальный элемент, основываясь на переданном компараторе;

findFirst() – вытаскивает первый элемент стрима:

allMatch(Predicate predicate) — возвращает true, если все элементы стрима удовлетворяют условию. Если встречается какой-либо элемент, для которого результат вызова функции-предиката будет false, то оператор перестаёт просматривать элементы и возвращает false:

anyMatch(Predicate predicate) — вернет true, если хотя бы один элемент стрима удовлетворяет условию predicate :

noneMatch(Predicate predicate) — вернёт true, если, пройдя все элементы стрима, ни один не удовлетворил условию predicate :

toList() — собирает элементы в List :

toSet() — cобирает элементы в множество:

counting() — Подсчитывает количество элементов:

joining(CharSequence delimiter, CharSequence prefix, CharSequence suffix) — cобирает элементы в одну строку. Дополнительно можно указать разделитель, а также префикс и суффикс для всей последовательности:

summingDouble(ToDoubleFunction mapper) — коллектор, который преобразовывает объекты в int/long/double и подсчитывает сумму.

Источник

Немного о Stream API(Java 8)

Небольшая статья с примерами использования Stream API в Java8, которая, надеюсь, поможет начинающим пользователям освоить и использовать функционал.
Итак, что такое Stream API в Java8? «Package java.util.stream» — «Classes to support functional-style operations on streams of elements, such as map-reduce transformations on collections». Попробую дать свой вариант перевода, фактически это — поддержка функционального стиля операций над потоками, такими как обработка и «свёртка» обработанных данных.
«Stream operations are divided into intermediate and terminal operations, and are combined to form stream pipelines. A stream pipeline consists of a source (such as a Collection, an array, a generator function, or an I/O channel); followed by zero or more intermediate operations such as Stream.filter or Stream.map; and a terminal operation such as Stream.forEach or Stream.reduce» — описание с сайта.
Попробуем разобраться в этом определении. Авторы говорят нам о наличии промежуточных и конечных операций, которые объедены в форму конвейеров. Потоковые конвейеры содержат источник (например, коллекции и т.п.) за которым следуют промежуточные и конечные операции и приводятся их примеры. Тут стоит заметить, что все промежуточные операции над потоками — ленивые(LAZY). Они не будут исполнены, пока не будет вызвана терминальная (конечная) операция.
Еще одна интересная особенность, это – наличие parallelStream(). Данные возможности я использую для улучшения производительности при обработке больших объемов данных. Параллельные потоки позволят ускорить выполнение некоторых видов операций. Я использую данную возможность, когда знаю, что коллекция достаточно большая для обработки ее в «ForkJoin» варианте. Подробнее про ForkJoin читайте в предыдущей статье на эту тему — «Java 8 в параллель. Учимся создавать подзадачи и контролировать их выполнение».

Закончим с теоретической частью и перейдем к несложным примерам.
Пример показывает нахождение максимального и минимального значения из коллекции.

Немного усложним пример и добавим исключения (в виде null) при максимального значения в пример №2.

Усложним примеры. Создадим коллекцию «спортивный лагерь», состоящую из полей «Имя» и «Количество дней в спортивном лагере». Сам пример создания класса ниже.

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

В примере было найдено имя, Ирина, которая будет находиться в лагере всех дольше.
Преобразуем пример и создадим ситуацию, когда у нас вкралась ошибка, и одна из записей null в имени.

В этом случае вы получите результат, равный «Name=null».Согласитесь, что мы хотели не этого.Немного изменим поиск по коллекции на новый вариант.

Полученный результат, «Ira» — верен.
В примерах показано нам нахождение минимальных и максимальных значений по коллекциям с небольшими дополнениями в виде исключения null значений.
Как мы говорили доступные методы можно разделить на две большие группы промежуточные операции и конечные. Авторы могут называть их различно, например, вариант названия конвейерные и терминальные методы употребляется в литературе и статьях. При работе с методами существует одна конструктивная особенность, вы можете «накидывать» множество промежуточных операций, в конце производя вызов одного терминального метода.
В новом примере добавим сортировку и вывод определенного элемента, например, добавим фильтр по именам с встречающимся «Ivan» и произведем подсчет таких элементов (исключим null значения).

Добавив в коллекцию new SportsCamp(«Ivan», 17), получим результат равный «countName=2». Нашли две записи.
В данных примерах использовалось создание стрима из коллекции, доступны и другие варианты, например, создание стрима из требуемых значений, например, Stream streamFromValues = Stream.of(«test1», «test2», «test3»), возможны и другие варианты.
Как говорилось выше, у пользователей есть возможность использовать «обработку» используя parallelStream().
Немного изменив пример, получим новый вариант реализации:

Особенность этого варианта состоит в реализации параллельного стрима. Хочется обратить внимание, что parallelStream() оправданно использовать на мощных серверах(многоядерных) для больших коллекций. Я не даю четкого определения и точного размера коллекций, т.к. очень много параметров необходимо выявить и просчитать. Часто только тестирование может показать вам увеличение производительность.

Мы немного познакомились с простыми операциями, поняли отличие между конвейерными и терминальными операциями, попробовали и те и другие. А теперь давайте посмотрим примеры более сложных операций, например, collect и Map, Flat и Reduce.
Еще раз заглянем в официальную документацию документацию и попробуем реализовать свои примеры.
В новом примере попробуем преобразовать одну коллекцию в другую, по именам начинающимся с «I» и запишем это в List.

Результат будет равен трём. Тут нужно обратить внимание, что порядок указания исключения null элементов значим.
Обратите внимание, что Collectors обладает массой возможностей, включая вывод среднего значения или информации со статистикой. Как пример, попробуем соединить данные, вот так:

Источник

Что нужно знать о Java Stream API

Авторизуйтесь

Что нужно знать о Java Stream API

Stream java что это. pp. Stream java что это фото. Stream java что это-pp. картинка Stream java что это. картинка pp

Java-разработчик

Всем привет! В этой статье я хочу познакомить вас, на мой взгляд, с одним из самых значительных нововведений в Java со времен ее появления — это Java Stream API.

Что такое Java Stream API? Зачем? И какие дает преимущества?

Очень часто, когда мы пишем программу, нам нужно обрабатывать наши данные. Для обработки данных мы используем циклы либо рекурсивные функции для обхода наших данных.

Java Stream API был создан для того, чтобы помочь пользователям ускорить и упростить обработку данных. Сам по себе API предоставляет инструмент, который позволяет нам дать рецепт того как обрабатывать объекты.

Если проводить параллели с реальным миром, то давайте представим, что у нас есть некий завод по производству мебели под заказ.

4–5 декабря, Онлайн, Беcплатно

Грузовые автомобили привозят бревна на завод. На данном заводе у нас есть люди которых мы обучили что-то делать с древесиной, чтобы из нее получилась мебель: они просматривают каждое бревно на предмет дефектов и отфильтровывают брак, распиливают бревна, обрабатывают доски, собирают при помощи гвоздей и клея и защищают готовую продукцию при помощи лака.

Последний элемент в этой цепи — покупатель, который приходит на завод и делает заказ.

Без покупателя нет смысла запускать все производство, поэтому весь процесс стартует во время запуска производства.

В мире Java такой завод называется Stream API. Этот API представляет собой библиотеку, которая помогает в функциональном стиле кратко, но емко описывать, как обработать данные.

Как и в примере про завод, у каждого стрима должен быть источник объектов. Этим источником информации чаще всего бывает коллекция, так как именно в них мы и храним наши данные, но это не обязательно — может быть и какой-то генератор, который генерирует объекты по заданному правилу, примеры мы рассмотрим позже.

В Java Stream API также предусмотрены промежуточные операции. Они выполняют роль рабочих. Операции описывают процесс обработки объектов.

В конце каждого стрима должна быть терминальная операция, которая должна поглотить все обработанные данные.

В примере про завод мы видели, что заказчик становится триггером начала производства и является последним звеном в работе завода — он забирает всю продукцию.

Рассмотрим простейший стрим. Создадим класс бревно и поместим несколько бревен в коллекцию:

Получив ссылку на стрим, мы можем начать обрабатывать поток наших данных.

Отфильтруем бревна, количество которых меньше 7 и оставим только те, которые не являются дубом. Выглядеть это будет так:

Мы добавили фильтры и получили стрим, в котором описан процесс обработки всех наших бревен. Теперь мы должны добавить к нему терминальную операцию, чтобы запустить поток данных из коллекции:

В этом примере конечная операция принимает оставшиеся элементы после фильтрации и распечатывает их. Стоит особо упомянуть, что второй раз вызвать терминальную операцию не получится — стрим является «одноразовым» объектом. Это сделано авторами библиотеки для того, чтобы можно было корректно обрабатывать данные, которые имеют ограниченное время жизни. Например, если обрабатывать пакеты из интернета, то данные в стрим могут попасть только один раз, поэтому повторный вызов теряет всякий смысл.

Как упоминалось ранее, создать источник данных можно разными способами. Рассмотрим самые популярные.

Способы создания источника данных

В начале пройдемся по методам объявленным в интерфейсе Stream.

Stream.of(). Метод принимает массив объектов и создает на их основе стрим.

Для создания пустого стрима существует метод:

Патерн строитель поддерживается библиотекой, потому получив объект строителя Stream.builder() мы можем сконструировать с помощью него новый стрим.

Если у нас есть два стрима, мы можем объеденить их в один вызвав метод:

В итоге мы получим стрим, в котором будет находится шесть элементов.

Стрим не обязательно должен поглощать какие-то данные, можно создать генератор, который будет поставлять в наш стрим с помощью метода generate()

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

Аналогичную функциональность предоставляет класс Random. В нем уже есть методы которые создают стримы из случайных чисел.

Тут стоить отметить, что порой, когда стрим состоит из одних чисел, использование оберток над примитивными типами будет сильно влиять на производительность.

Поэтому создатели стримов добавили специальные типы стримов для примитивных типов:

Это такие же стримы, но как понятно из названия оперируют они только одним типом данных.

Теперь мы перейдем к самому интересному — в интерфейсе Collection добавлен дефолтный метод, который возвращает нам стрим. То есть любая коллекция дает нам возможность превратить ее в стрим:

Просто вызвав метод у коллекции мы получили стрим. Это самый частый способ получить стрим из набора данных.

Познакомившись с основными методами создания теперь мы можем перейти к промежуточным операциям. Именно они позволят нам обработать наш поток данных.

Промежуточные операции

Мы ранее уже знакомились с операцией фильтр, она позволяет нам написать выражение, которое будет проверятся для каждого элемента и если выражение истинно, то элемент может проходить дальше.

Но на нашем заводе мы делаем намного больше чем просто фильтруем бревна. Для того, чтобы дерево превратилось в мебель его нужно преобразовать.

Для этого пригодится самая популярная функция — map().

Возьмем наш пример выше и попробуем преобразовать

Промежуточные операции можно конкатенировать между собой, то есть мы можем добавить еще несколько преобразований:

Во втором преобразовании мы разбили каждую строку на массив строк. Но если мы запустим приложение, мы увидим, что на экран не вывелись строки, а вывелось toString() массивов. Нам хочется чтобы стрим был плоский — то есть только из объектов, а не из других стримов/массивов в которых есть объекты. Для этого авторы Java Stream API придумали еще одну промежуточную операцию — flatMap. Вот как она позволит изменить нам наш стрим (для более краткой записи я заменил прошлые операции на метод референс):

Но запустив пример выше мы получили стрим стримов — Stream

Но так с ним работать не удобно, а обычный flatMap не сработает, поэтому для примитивных стримов существуют специальные операции для их преобразований:

Для того чтобы отсортировать буквы воспользуемся операцией sorted() :

Стоит отметить, что операция sorted() таит в себе некоторые проблемы. Так для того чтобы отсортировать объекты, поступающие из стрима, она должна аккумулировать в себе все объекты, которые есть в стриме и только потом приступить к сортировке. Но что делать, если стрим бесконечный либо в стриме огромное количество элементов? Вызов такой операции приведет к OutOfMemoryException.

Простой пример приведен ниже:

Стрим будет генерировать новые значения, пока остаток от деления на 7 сгенерированного значения не будет равен 0.

Терминальные операции

После знакомства с основными промежуточными операциями мы плавно подошли к заключению. Осталось рассмотреть терминальные операции. Это операции, которые как бы «запускают» наш стрим. Мы можем создать стрим и добавить в него любое количество промежуточных операций, но они не будут выполнены пока не будут добавлена терминальная операция.

Кроме этого терминальная операция может и возвращать значение. Рассмотрим самые распространенные — findFirst(), findAny(), anyMatch(), allMatch(), noneMatch().

Теперь стоит перейти к более сложным функциям. Часто в качестве результата стрима мы хотим получить набор из новых объектов, которые были созданы в результате обработки. Для этого удобно поместить их в массив или коллекцию.

В Java Stream API было добавлено несколько методов, которые дают соответствующую функциональность.

Вызвав терминальную операцию Object[] toArray() мы получим ссылку на массив, в котором будет находится все объекты. Если нужно вернуть массив определенного типа, то в метод стоить передать IntFunction generator на вход функции поступит число элементов, а внутри нее мы должны создать нужный нам тип массива.

Следующая операция, которую стоить упомянуть T reduce(T identity, BinaryOperator accumulator) — в нее передается начальное значение и бинарная функция, которая задает алгоритм объединения двух объектов.

Для того, чтобы получить сумму первых 100 членов стрима из произвольных значений, запишем:

Мы передаем начальный элемент для сложения, в нашем случае он 0 и бинарную функцию, которая описывает как объединить два значения из стрима.

Если же мы хотим перенести этот набора чисел в коллекцию, то для этого нам надо будет указать как создать коллекцию и как в нее помещать элементы:

В функции reduce мы передали наш начальный аргумент — новую пустую коллекцию. Потом описали правило, по которому будем объединять коллекцию и элементы стрима.

И в конце описали как мы будем объединять две коллекции.

То есть вся логика комбинирования элементов хранится в структуре данных под названием коллектор.

Создатели Java Stream API добавили в библиотеку большое количество коллекторов, рассмотрим их.

Существует более общий метод Collectors.toCollection(). В качестве аргумента в нее можно передать коллекцию, в которую будут помещены элементы стрима.

Операция partitionBy() позволяет разделить стрим на два множества по определенному условию. Например мы хотим разделить наш буквенный стрим на две группы с большими буквами и прописными:

Коллекторы могут быть скомбинированы друг с другом, что дает большую гибкость.

В примере выше мы видим, что некоторые буквы повторяются, мы этого не хотим поэтому добавим еще один коллектор, который соберет все в Set:

Чтобы самостоятельно реализовать коллектор можно воспользоваться статическим методом:

В этой короткой статье мы познакомились с, на мой взгляд, самой крутой штукой в языке Java с момента ее создания. Стримы позволяют существенно упростить, а соответственно ускорить разработку кода. Возможность практически бесплатно сделать стрим параллельным, тем самым повысив производительность кода в разы, делает стримы инструментом номер одни в руках каждого разработчика.

Источник

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *