Подробное руководство по потокам Java в Java 8

Подробное руководство по потокам Java в Java 8

9 ноября 2022 г.

Введение в потоки Java в Java 8

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

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

  1. Потоки Java можно использовать с помощью пакета java.util.stream в Java. Это можно импортировать в скрипт с помощью инструкции:

`import java.util.stream.* ;`

Using this, we can also implement multiple in-built functions on Java streams with ease.

  1. Поток Java не является структурой данных. Он может принимать входные данные из коллекций данных, таких как коллекции и массивы в Java.

3. Поток Java не требует изменения структуры входных данных.

4. Поток Java не изменяет исходный код. Вместо этого он генерирует выходные данные с помощью соответствующих конвейерных методов.

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

6. В потоках Java промежуточные операции конвейерны и лениво оцениваются. Они завершаются терминальными функциями. Это формирует базовый формат использования потока Java.

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

Создание потоков Java в Java 8

Потоки Java можно создавать несколькими способами. Некоторые из них перечислены в этом разделе следующим образом:

  1. Создание пустого потока с помощью метода Stream.empty()

Можно создать пустой поток для использования на более поздних этапах кода. При использовании метода «Stream.empty()» будет создан пустой поток, не содержащий значений. Этот пустой поток может пригодиться, когда мы хотим пропустить исключение нулевого указателя во время выполнения. Для этого можно использовать следующую команду:

Поток<String> ул = Stream.empty();

Приведенный выше оператор сгенерирует пустой поток с именем «str» без каких-либо элементов внутри него. Это можно проверить, проверив количество или размер потока с помощью термина str.count(). Например,

System.out.println(str.count()); n

Этот оператор печати выведет в результате "0".

  1. Создание потока с помощью метода Stream.builder() с экземпляром Stream.Builder

п

Мы также можем использовать Stream Builder для создания потока с помощью компоновщика. Построитель — это, по сути, шаблон для пошагового построения объектов. Давайте посмотрим, как мы можем создать экземпляр потока с помощью Stream Builder.

Stream.Builder<Integer> numBuilder = Stream.builder();

numBuilder.add(1).add(2).add( 3);

Поток<Целое> numStream = numBuilder.build();

Используя это, можно создать поток с именем «numStream», содержащий некоторые элементы «int». Это делается быстро с помощью экземпляра Stream.Builder «numBuilder», созданного первым.

  1. Создание потока с указанными значениями с помощью метода Stream.of()

Другой способ создания потока — с помощью метода «Stream.of()». Это простой способ создания потока с заданными значениями. Он объявляет, а также инициализирует поток. Ниже приведен пример использования метода «Stream.of()» для создания потока:

Поток<Целое> numStream = Stream.of(1, 2, 3);

Это создаст поток, содержащий элементы «int», как мы сделали в предыдущем методе, который включал экземпляр «Stream.Builder». Здесь мы напрямую создали поток, используя «Stream.of()» с заранее заданными значениями [1, 2 и 3].

  1. Создание потока из существующего массива с помощью метода Arrays.stream()

Другой распространенный метод создания потока — использование массивов в java. Поток также можно создать с помощью метода «Arrays.stream()». Это создает поток из существующего массива. Все элементы массива преобразуются в элементы потока. Вот пример того, как это можно сделать:

Целое число[] arr = {1, 2, 3, 4, 5};

Поток<Целое> numStream = Arrays.stream(arr);

Этот код будет генерировать поток «numStream», содержащий содержимое массива с именем «arr», который представляет собой целочисленный массив.

  1. Объединение двух существующих потоков с помощью метода Stream.concat()

Другой метод, который можно использовать для создания потока, — это метод «Stream.concat()». Этот метод используется для объединения двух потоков для создания единого потока. Оба потока объединяются по порядку. Это означает, что первым идет первый поток, за которым следует второй поток в последнем потоке. Пример такой конкатенации выглядит следующим образом:

Поток<Целое> numStream1 = Stream.of(1, 2, 3, 4, 5);

Поток<Целое> numStream2 = Stream.of(1, 2, 3);

Поток<Целое> комбинированныйпоток = Stream.concat( numStream1, numStream2);

Приведенный выше оператор создаст конечный поток с именем «combinedStream», содержащий элементы первого потока «numStream1» и второго потока «numStream2» один за другим.

Тип операций с потоками Java

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

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

Некоторыми примерами промежуточных операций являются следующие методы:

filter(), map(), different(), peek(), sorted() и т. д.

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

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

forEach(), collect(), count(), reduce() и т. д.

Потоковые операции Java (примеры)

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

Вот несколько примеров некоторых промежуточных операций, которые можно применять к потокам Java:

1 – фильтр()

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

Код:

Поток<Целое> numStream = Stream.of(43, 65, 1, 98, 63); Список<Целое число> даже = numStream.filter(n -> n % 2 == 0) .collect(Коллекторы.toList()); System.out.println(четный);

Вывод:

[98]

Пояснение:

В этом примере видно, что четные элементы (делящиеся на 2) фильтруются с помощью метода filter() и сохраняются в целочисленном списке «numStream», содержимое которого распечатывается позже. Поскольку 98 — единственное четное число в потоке, оно печатается в выводе.

2 – карта()

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

Пример того же:

Код:

Поток<Целое> numStream = Stream.of(43, 65, 1, 98, 63); Список<Целое число> d = numStream.map(n -> n*2) .collect(Коллекторы.toList()); System.out.println(d);

Вывод:

[86, 130, 2, 196, 126]

Пояснение:

Здесь мы видим, что метод map() используется для отображения каждого элемента потока «numStream» на 2, или удваивает значение каждого элемента потока. Выполненное здесь сопоставление представляет собой умножение на 2. Как видно из выходных данных, каждый из элементов в потоке успешно удвоен.

3 – отдельные()

Этот метод используется для извлечения только отдельных элементов в потоке путем фильтрации дубликатов. Пример того же:

Код:

Stream numStream = Stream.of(43,65,1,98,63,63,1); Список<целое> numList = numStream.distinct() .collect(Коллекторы.toList()); System.out.println(numList);

Вывод:

[43, 65, 1, 98, 63]

Пояснение:

В этом случае метод distinct() используется для "numStream". чтобы извлечь все отдельные элементы в списке «numList», удалив дубликаты из потока. Как видно из выходных данных, дубликатов нет, в отличие от входного потока, в котором изначально было два дубликата (63 и 1).

4 — peek()

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

Код:

Поток<Целое> numStream = Stream.of(43, 65, 1, 98, 63); Список<Целое число> nList = numStream.map(n -> n*10) .peek(n->System.out.println("Сопоставлено: "+ n)) .collect(Коллекторы.toList()); System.out.println(nList);

Вывод:

Сопоставлено: 430 Нанесено на карту: 650 Сопоставлено: 10 Сопоставлено: 980 Нанесено на карту: 630 [430, 650, 10, 980, 630]

Пояснение:

Здесь метод peek() используется для получения промежуточных результатов, поскольку метод map() применяется к элементам потока. Здесь мы можем заметить, что даже до применения терминальной операции collect() для печати окончательного содержимого списка в следующем операторе «print» результат для каждого сопоставления элемента потока печатается последовательно заранее.

5 – отсортировано()

Метод sorted() используется для сортировки элементов потока. По умолчанию он сортирует элементы в порядке возрастания. Также можно указать конкретный порядок сортировки в качестве параметра. Ниже приведен пример реализации этого метода:

Код:

Поток<Целое> numStream = Stream.of(43, 65, 1, 98, 63); numStream.sorted().forEach(n -> System.out.println(n));

Вывод:

1 43 63 65 98

Пояснение:

Здесь метод sorted() используется для сортировки элементов потока в порядке возрастания по умолчанию (поскольку конкретный порядок не указан). Элементы, напечатанные в выводе, можно увидеть в порядке возрастания.

н

Операции с терминалом

Вот несколько примеров некоторых терминальных операций, которые можно применять к потокам Java:

1 — forEach()

Метод forEach() используется для перебора всех элементов потока и выполнения функции для каждого элемента один за другим. Это действует как альтернатива операторам цикла, таким как «для», «пока» и т. д. Пример того же самого приведен ниже:

Код:

Stream numStream = Stream.of(43, 65, 1, 98, 63); numStream.forEach(n -> System.out.println(n));

Вывод:

43 65 1 98 63

Пояснение:

Здесь метод forEach() используется для печати каждого элемента потока один за другим.

2 – count()

Метод count() используется для извлечения общего количества элементов, присутствующих в потоке. Это похоже на метод size(), который часто используется для определения общего количества элементов в коллекции. Ниже приведен пример использования метода count() с потоками Java:

Код:

Stream numStream = Stream.of(43, 65, 1, 98, 63); System.out.println(numStream.count());

Вывод:

5

Пояснение:

Поскольку поток «numStream» содержит 5 целочисленных элементов, при использовании метода count() на выходе получается «5».

3 – сбор()

Метод collect() используется для выполнения изменяемых сокращений элементов потока. Его можно использовать для удаления содержимого из потока после завершения обработки. Он использует

Класс-коллектор в Java для выполнения сокращений.

Код:

Поток<Целое> numStream = Stream.of(43, 65, 1, 98, 63); Список<Целое число> нечетный = numStream.filter(n -> n % 2 == 1) .collect(Коллекторы.toList()); System.out.println(нечетный);

Вывод:

[43, 65, 1, 63]

Пояснение:

В этом примере все нечетные элементы (которые не делятся на 2) в потоке фильтруются и собираются/сокращаются в список под названием «нечетные». В конце печатается список «нечетных».

4 — min() и max()

Метод min(), как следует из названия, может использоваться в потоке для поиска минимального элемента в этом потоке. Точно так же метод max() можно использовать для поиска максимального элемента в потоке. Давайте попробуем понять, как их можно использовать, на примере.

Код:

Поток<Целое> numStream = Stream.of(43, 65, 1, 98, 63); int наименьший = numStream.min((m, n) -> Integer.compare(m, n)).get(); System.out.println("Самый маленький элемент: " + самый маленький);

numStream = Stream.of(43, 65, 1, 98, 63); int наибольший = numStream.max((m, n) -> Integer.compare(m, n)).get(); System.out.println("Самый большой элемент: " + самый большой);

Вывод:

Самый маленький элемент: 1 Самый большой элемент: 98

Пояснение:

В этом примере мы напечатали наименьший элемент в потоке «numStream» с помощью метода min(), а самый большой элемент — с помощью метода max().

Обратите внимание, что здесь мы снова добавили элементы в поток «numStream» перед применением метода max(). Это связано с тем, что min() является терминальной операцией и уничтожает содержимое исходного потока, возвращая только окончательный результат (в данном случае это целочисленное «наименьшее» значение).

5 — findAny() и findFirst()

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

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

Код:

Поток<Целое> numStream = Stream.of(43, 65, 1, 98, 63); Необязательный <Целое число> opt = numStream.findFirst(); System.out.println (opt); numStream = Поток.пусто(); opt = numStream.findAny(); System.out.println(opt);

Вывод:

Необязательно[43] Необязательно.пусто

Пояснение:

Здесь, в первом случае, метод findFirst() возвращает первый элемент потока как необязательный. Затем, когда поток переназначается как пустой поток, метод findAny() возвращает пустой необязательный параметр, как было заявлено выше.

6 — allMatch(), anyMatch() и noneMatch()

Метод allMatch() используется для проверки того, соответствуют ли все элементы в потоке определенному предикату, и возвращает логическое значение «true», если это так, в противном случае возвращается «false». Если поток пуст, он возвращает «true»

Метод anyMatch() используется для проверки того, соответствует ли какой-либо элемент в потоке определенному предикату. Он возвращает «true», если это так, и «false» в противном случае. Если поток пуст, он возвращает «false».

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

Пример, иллюстрирующий это, следующий:

Код:

Поток<Целое> numStream = Stream.of(43, 65, 1, 98, 63); логический флаг = numStream.allMatch(n -> n1); System.out.println(флаг); numStream = Stream.of(43, 65, 1, 98, 63); флаг = numStream.anyMatch(n -> n1); System.out.println(флаг); numStream = Stream.of(43, 65, 1, 98, 63); флаг = numStream.noneMatch(n -> n==1);System.out.println(flag);

Вывод:

ложь истинный ложь

Пояснение:

Для данного потока «numStream», содержащего 1 в качестве элемента, метод allMatch() возвращает false, так как все элементы равны не 1, а только одному из них; метод anyMatch() возвращает true, поскольку хотя бы один из элементов равен 1; Метод noneMatch() возвращает false, поскольку 1 действительно существует как элемент в потоке.

n Отложенные вычисления в потоках Java

Ленивые вычисления приводят к замечательной оптимизации при работе с Java Streams в Java 8. Они в основном связаны с задержкой выполнения промежуточных операций до тех пор, пока не встретится терминальная операция.

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

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

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

Конвейеры в потоках Java

Конвейер, в случае потоков Java, включает входной поток, ноль или несколько промежуточных операций, выстроенных одна за другой, и, наконец, терминальную операцию. нет

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

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

Заключение

А теперь подведем итог тому, что мы уже изучили. В этой статье

  1. Мы кратко рассмотрели, что такое потоки Java.
  2. Затем мы изучили множество различных методов создания потоков Java в Java 8.
  3. Далее мы изучили два важных типа операций (промежуточные операции и терминальные операции), которые можно выполнять с потоками Java.
  4. Затем, после этого, мы подробно рассмотрели некоторые примеры как промежуточных, так и терминальных операций.
  5. В конце концов, мы более подробно узнали о ленивых вычислениях и, наконец, изучили конвейерную обработку в потоках Java.

н

н


Оригинал
PREVIOUS ARTICLE
NEXT ARTICLE