Поток программирование java

Java Stream API предоставляет функциональный подход к обработке коллекций объектов. API Java Stream был добавлен в Java 8 вместе с несколькими другими функциональными функциями программирования. Этот учебник по потоку Java объяснит, как работают эти функциональные потоки и как вы их используете. API Java Stream API не связан с Java InputStream и Java OutputStream Java IO. InputStreamИ OutputStreamсвязаны с потоками байтов. Java Stream API предназначен для обработки потоков объектов, а не байтов.

Java Stream API Tutorial — Видеоверсия

У меня есть видео версия этого урока здесь:

Определение потока Java

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

В отличие от этого, когда вы используете функции итерации коллекций Java (например, итератор Java или цикл Java for-each. Используемый с итерацией Java), вы должны реализовать итерацию элементов самостоятельно.

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

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

Это называется потоковой обработкой.

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

Получить поток

Существует много способов получить поток Java. Один из наиболее распространенных способов получить aStream-это получить его из коллекции Java. Вот пример получения a Streamиз списка Java:

добавить(добавить(добавить(

Этот пример сначала создает Java

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

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

StreamИнтерфейс имеет выбор терминальных и нетерминальных операций. Нетерминальная потоковая операция-это операция. Которая добавляет слушателя в поток, не делая ничего другого. Терминальная потоковая операция — это операция. Которая запускает внутреннюю итерацию элементов. Вызывает все прослушиватели и возвращает результат.

Вот пример потока Java, который содержит как нетерминальную, так и терминальную операцию:

импорт java.util.ArrayList; импорт java.util.List; импорт java.util.stream.Stream; публичный класс StreamExamples { public static void main(String[] args) { StringList.add(StringList.add(StringList.add(long count = поток .граф(); 

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

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

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

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

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

StringList.add(StringList.add(StringList.add(

Обратите внимание на призыв stream map()к. Этот вызов фактически возвращает новый Streamэкземпляр. Представляющий исходный поток строк с примененной операцией сопоставления.

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


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

Довольно часто на Java связывают вызовы с нетерминальными операциями Stream. Вот пример цепочки нетерминальных вызовов операций в потоках Java:


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

Фильтр()

Java Stream filter()можно использовать для фильтрации элементов из Java Stream. filterМетод принимает aPredicate, который вызывается для каждого элемента в потоке. Если элемент должен быть включен в результирующий Streamрезультат , Predicateто он должен вернуться true. Если элемент не должен быть включен, Predicateто он должен вернуться false.

Вот пример вызова метода JavaStream filter():


Карта()

Метод Java Stream map()преобразует (отображает) элемент в другой объект. Например, если у вас есть список строк, он может преобразовать каждую строку в нижний регистр. Верхний регистр или в подстроку исходной строки или что-то совершенно другое. Вот пример JavaStream map():


flatMap()

Методы Java Stream flatMap()отображают один элемент в несколько элементов. Идея состоит в том, что вы

Например, представьте, что у вас есть объект с вложенными объектами (дочерние объекты). Затем вы можете отобразить этот объект в Вы также можете сопоставить поток Lists элементов с самими элементами. Или сопоставьте поток строк с потоком слов в этих строках — или с отдельными Characterэкземплярами в этих строках.

Вот пример, который flatmaps a Listстрок к словам в каждой строке. Этот пример должен дать вам представление о том. Как flatMap()можно использовать для отображения одного элемента на несколько элементов.

StringList.add(StringList.add(StringList.add(String[] split = value.split(Arrays.asList(split).stream(); 

Этот Stream flatMap()пример Java сначала создает a Listс 3 строками , содержащими названия книг. Тогда а Streamдля Listтого получается, иflatMap() называемый.

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

Обратите внимание, что этот пример заканчивается вызовомforEach(), который является терминальной операцией. Этот вызов существует только для того. Чтобы вызвать внутреннюю итерацию и, следовательно, операцию плоской карты. Если бы в цепочке не было вызвано ни одной терминальной операцииStream, ничего бы не произошло. Никакого плоского отображения на самом деле не было бы.

отчетливый()

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

StringList.add(StringList.add(StringList.add(StringList.add(.отчетливый() .собирать(коллекционеры.тоЛист()); System.out.println(distinctStrings); 

В этом примере элемент oneпоявляется в исходном потоке 2 раза. Только первое вхождение этого элемента будет включено в Streamвозвращаемое значение by distinct(). Таким образом, результирующее List(от вызова collect()) будет содержать только one, twoи three. Выходные данные, напечатанные в этом примере, будут следующими::

[раз, два, три] 

предел()

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

StringList.add(StringList.add(StringList.add(StringList.add(течение .предел(2) 

Этот пример сначала создает a Stream, затем вызывает limit()его, а затем вызывает forEach()с помощью лямбды. Которая выводит элементы в потоке. Из-за вызова будут напечатаны только два первых элементаlimit(2).

заглядывать()

Метод Java Stream peek()является нетерминальной операцией. Которая принимает a Consumer(java.util.function.Consumer) в качестве параметра. ConsumerБудет вызываться для каждого элемента в потоке. peek()Метод возвращает новыйStream, который содержит все элементы в исходном потоке.

Цель peek()метода, как говорится в методе, состоит в том, чтобы взглянуть на элементы в потоке, а не преобразовывать их. Имейте в виду, что peekметод не запускает внутреннюю итерацию элементов в потоке. Для этого нужно вызвать терминальную операцию. Вот пример JavaStream peek():

StringList.add(StringList.add(

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

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

Операция терминала обычно не возвращает новый Streamэкземпляр. Таким образом. Как только вы вызываете терминальную операцию в потоке. Цепочка Streamэкземпляров из не терминальной операции заканчивается. Вот пример вызова терминальной операции на JavaStream:

long count = поток .граф(); 

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

Любой матч()

Метод JavaStream anyMatch()-это терминальная операция. Которая принимает один Predicateпараметр as . Запускает внутреннюю итерацию Streamи применяет Predicateпараметр к каждому элементу. Если Predicateвозвращает true для любого из элементов, anyMatch()метод возвращает true. Если никакие элементы не совпадают Predicate, anyMatch()вернется false. Вот пример JavaStream anyMatch():

StringList.add(StringList.add(StringList.add(System.out.println(anyMatch); 

В приведенном выше примере anyMatch()вызов метода будет возвращенtrue, поскольку первый строковый элемент в потоке начинается с

allMatch()

Метод JavaStream allMatch()-это терминальная операция. Которая принимает один Predicateпараметр as . Запускает внутреннюю итерацию элементов Streamи применяет этот Predicateпараметр к каждому элементу. Если Predicateвозвращает trueдля всех элементов в Stream, то allMatch()будет возвращать true. Если не все элементы совпадают Predicate, allMatch()метод возвращает falseрезультат . Вот пример JavaStream allMatch():

StringList.add(StringList.add(StringList.add(System.out.println(allMatch); 

В приведенном выше примере allMatch()метод будет возвращенfalse, поскольку только одна из строк в Streamначале

noneMatch()

Метод Java Stream noneMatch()является терминальной операцией . Которая будет повторять элементы в потоке и возвращать trueилиfalse, в зависимости от того. Соответствуют ли никакие элементы в потоке Predicateпереданному noneMatch()параметру to as. noneMatch()Метод будет возвращен true, если никакие элементы не совпадаютPredicate, и falseесли один или несколько элементов совпадают. Вот пример JavaStream noneMatch():

StringList.add(StringList.add(System.out.println(

собирать()

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

StringList.add(StringList.add(StringList.add(тоЛист()); System.out.println(stringsAsUppercaseList); 

collect()Метод принимает a Collector(java.util.stream.Collector) в качестве параметра. Реализация a Collectorтребует некоторого изучения Collectorинтерфейса. К счастью, класс Java java.util.stream.Collectorsсодержит набор предварительно реализованных Collectorреализаций. Которые можно использовать для наиболее распространенных операций. В приведенном выше примере была Collectorиспользована реализация, возвращеннаяCollectors.toList(). Это Collectorпросто собирает все элементы в потоке в стандартном Java List

считать()

Метод JavaStream count()-это терминальная операция , которая запускает внутреннюю итерацию элементов вStream, и подсчитывает элементы. Вот пример JavaStream count():

StringList.add(StringList.add(StringList.add(String[] split = value.split(Arrays.asList(split).stream(); }) .граф(); System.out.println(

Этот пример сначала создает a Listиз строк, затем получает Streamfor that List, добавляет flatMap()для него операцию a. А затем завершает вызовом to count(). count()Метод начнет итерацию элементов в системеStream, которая приведет к тому. Что строковые элементы будут разбиты на слова в ходе flatMap()операции. А затем подсчитаны. Окончательный результат. Который будет напечатан, — 14.

финдАни()

Метод Java Stream findAny()может найти один элемент из потока. Найденный элемент может быть взят из любой точки Streamмира . Нет никакой гарантии относительно того. Откуда в потоке берется элемент. Вот пример JavaStream findAny():

StringList.add(StringList.add(StringList.add(StringList.add(System.out.println(anyElement.get()); 

Обратите внимание. Как findAny()метод возвращает an Optional. Он Streamможет быть пустым — поэтому ни один элемент не может быть возвращен. Вы можете проверить. Был ли элемент найден с помощью этого Optional isPresent()метода.

findFirst()

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

StringList.add(StringList.add(StringList.add(StringList.add(System.out.println(result.get()); 

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

инструкция foreach()

Метод Java Stream forEach()— это терминальная операция , которая запускает внутреннюю итерацию элементов вStream, и применяет a Consumer(java.util.function.Consumer) к каждому элементу в Stream. forEach()Метод возвращается void. Вот пример JavaStream forEach():

StringList.add(StringList.add(StringList.add(StringList.add(

минута()

Метод Java Stream min()является терминальной операцией. Которая возвращает наименьший элемент в Stream. Какой элемент является наименьшим, определяется Comparatorреализацией. Которую вы передаете min()методу. Я объяснил, как Comparatorработает интерфейс в моем учебнике о сортировке коллекций Java. Вот пример JavaStream min():

StringList.add(StringList.add(String minString = min.get(); System.out.println(minString); 

Обратите внимание. Как min()метод возвращает Optionalрезультат. Который может содержать или не содержать результат. Если Streamпустое, Optional get()метод будет выбрасывать a NoSuchElementException.

максимум()

Метод Java Stream max()является терминальной операцией. Которая возвращает самый большой элемент в Stream. Какой элемент является самым большим, определяется Comparatorреализацией. Которую вы передаете max()методу. Я объяснил, как Comparatorработает интерфейс в моем учебнике о сортировке коллекций Java. Вот пример JavaStream max():

StringList.add(StringList.add(String maxString = max.get(); System.out.println(maxString); 

Обратите внимание. Как max()метод возвращает anOptional, который может содержать или не содержать результат. Если значение Streamпусто, то Optional get()метод выдаст a NoSuchElementException.

уменьшить()

Метод JavaStream reduce()-это терминальная операция. Которая может свести все элементы потока к одному элементу. Вот пример JavaStream reduce():

StringList.add(StringList.add(StringList.add(System.out.println(reduced.get()); 

Обратите внимание на Optionalвозвращаемый reduce()метод. ЭтоOptional содержит значение (если таковое имеется), возвращаемое лямбда — выражением, переданным reduce()методу. Вы получаете это значение. Вызывая Optional get()метод.

В смятении()

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

StringList.add(StringList.add(StringList.add(Object[] objects = stream.toArray(); 

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

Интерфейс Java Streamсодержит вызываемый статический методconcat(), который может объединить два потока в один. В результате получается новыйStream, который содержит все элементы из первого потока. А затем все элементы из второго потока. Вот пример использования метода JavaStream concat():

StringList.add(StringList.add(StringList.add(stringList2.add(stringList2.add(тоЛист()); System.out.println(stringsAsUppercaseList); 

Создать Поток Из Массива

Интерфейс Java Streamсодержит вызываемый статический методof(), который может быть использован для создания a Streamиз одного или нескольких объектов. Вот пример использования Java Stream of()metho:


Критика Java Stream API

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

Пакетная, Не Потоковая

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

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

Цепочка, А Не Граф

API Java Stream разработан таким образом. Что Streamэкземпляр может быть обработан только один раз. Другими словами, вы можете добавить только одну нетерминальную операцию к a Stream, что приведет к созданию нового Streamобъекта. Вы можете добавить еще одну нетерминальную операцию к полученному Streamобъекту. Но не к первому. Полученная структура нетерминальных Streamэкземпляров образует цепочку.

В истинном потоковом API обработки корневой поток и прослушиватели событий обычно образуют граф, а не просто цепочку. Несколько слушателей могут прослушивать корневой поток. И каждый слушатель может обрабатывать элементы в потоке по-своему и в результате пересылать преобразованный элемент. Таким образом, каждый слушатель (нетерминальная операция) может, как правило, действовать как сам поток. Который другие слушатели могут слушать результаты. Именно так спроектирован Apache Kafka Streams. Каждый слушатель (промежуточный поток) также может иметь несколько слушателей. Результирующая структура образует граф слушателей со слушателями со слушателями и т. Д.

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

Когда ваша структура обработки потока может представлять собой график с несколькими конечными операциями. Stream API не может легко поддерживать терминальные операции. Как это делает Java Stream API. Чтобы легко поддерживать терминальные операции. Должна быть одна заключительная операция. Из которой возвращается конечный результат. API потоковой обработки на основе графа может вместо этого поддерживать операцию

Внутренняя, А Не Внешняя Итерация

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

Некоторые API потоковой обработки на основе графов также предназначены для того. Чтобы как бы скрыть итерацию элементов от пользователя API (например. Apache Kafka Streams и RxJava). Однако лично я предпочитаю дизайн, в котором каждый узел потока (корневой поток и слушатели) может иметь элементы. Переданные им через вызов метода. И этот элемент должен быть передан через полный граф для обработки. Такой дизайн облегчил бы тестирование каждого слушателя в графике. Так как вы можете настроить график и протолкнуть элементы через него позже. И, наконец. Проверить результат (выборочное состояние графика). Такая конструкция также позволила бы графу потоковой обработки иметь элементы. Помещенные в него через несколько узлов в графе. А не только через корневой поток.