Статьи

Станьте мастером потоков Java. Часть 1. Создание потоков

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

Вся идея Streams состоит в том, чтобы представить конвейер, по которому будут проходить данные, а функции конвейера работают с данными. Таким образом, операции в функциональном стиле на потоках элементов могут быть выражены. Эта статья — первая из пяти, в которой вы узнаете, как стать Мастером потоков. Мы начнем с простых примеров потоков и продолжим выполнение более сложных задач, пока не научимся подключать стандартные потоки Java к базам данных в облаке.

Вся идея Streams состоит в том, чтобы представить конвейер, по которому будут проходить данные, а функции конвейера работают с данными. Таким образом, операции в функциональном стиле на потоках элементов могут быть выражены. Эта статья — первая из пяти, в которой вы узнаете, как стать Мастером потоков. Мы начнем с простых примеров потоков и продолжим выполнение более сложных задач, пока не научимся подключать стандартные потоки Java к базам данных в облаке.

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

Вот краткое изложение предстоящих статей:

Поскольку мы твердо верим в концепцию «Учиться на собственном опыте», серия дополняется репозиторием GitHub, который содержит упражнения Stream, разделенные на 5 единиц — каждая соответствует теме статьи. Инструкции по использованию исходного кода приведены в README-файле.

Что такое потоки Java?

Интерфейс Java Stream был впервые представлен в Java 8 и вместе с лямбдами выступает вехой в развитии Java, поскольку он вносит большой вклад в упрощение декларативного (функционального) стиля программирования. Если вы хотите узнать больше о преимуществах декларативного кодирования, мы отсылаем вас к этой статье .

Поток Java можно визуализировать как конвейер, по которому будут передаваться данные (см. Изображение ниже). Функции конвейера будут работать с данными, например, фильтруя, отображая и сортируя элементы. Наконец, операция терминала может быть выполнена для сбора элементов в предпочтительной структуре данных, такой как
List , Array или Map . Важно отметить, что поток может быть использован только один раз.

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

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

1
2
3
4
5
6
List <String> list = Stream.of("Monkey", "Lion", "Giraffe","Lemur")
    .filter(s -> s.startsWith("L"))
    .map(String::toUpperCase)
    .sorted()
    .collect(toList());
System.out.println(list);

Поскольку Stream API является описательным и чаще всего интуитивно понятным в использовании, вы, вероятно, будете достаточно хорошо понимать смысл этих операций независимо от того, встречали ли вы их раньше или нет. Мы начнем с потока List содержащего четыре строки, каждая из которых представляет африканское животное. Затем операции отфильтровывают элементы, начинающиеся с буквы «L», преобразуют оставшиеся элементы в заглавные буквы, сортируют их в естественном порядке (что в данном случае означает алфавитный порядок) и, наконец, собирают их в List . Следовательно, в результате [“LEMUR”, “LION”] .

Важно понимать, что потоки «ленивы» в том смысле, что элементы «запрашиваются» операцией терминала (в этом случае
.collect() заявление). Если для работы терминала требуется только один элемент (как, например, операция терминала .findFirst() ), то самое большее один элемент когда-либо достигнет операции терминала, и элементы напоминания (если они есть) никогда не будут созданы источник. Это также означает, что просто создание потока часто является дешевой операцией, в то время как потребление может быть дорогостоящим в зависимости от конвейера потока и количества потенциальных элементов в потоке.

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

Источники потока

Потоки в основном подходят для обработки коллекций объектов и могут работать с элементами любого типа T Хотя существует три специальные реализации Stream; IntStream , LongStream и LongStream , которые ограничены обработкой соответствующих примитивных типов.

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

1
2
3
4
Stream <T>     Stream.empty()
IntStream  IntStream.empty()
LongStream  LongStream.empty()
DoubleStream  DoubleStream.empty()

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

Полезные IntStreams

Основной случай — генерирование потока из небольшого количества элементов. Это может быть достигнуто путем перечисления целых чисел с помощью IntStream.of (). Код ниже выдает простой поток элементов 1, 2 и 3.

1
IntStream oneTwoThree = IntStream.of(1, 2, 3);

Перечисление всех элементов вручную может быть утомительным, если количество элементов увеличивается. В случае, когда нас интересуют значения в определенном диапазоне, команда .rangeClosed() более эффективна. Операция является включающей, что означает, что следующий код создаст поток всех элементов от 1 до 9.

1
IntStream positiveSingleDigits = IntStream.rangeClosed(1, 9);

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

1
IntStream powersOfTwo = IntStream.iterate(1, i -> i * 2);

Есть также несколько, возможно, более неожиданных способов создания потока. Метод chars () можно использовать для потоковой передачи по символам в
String , в данном случае, элементы «А», «В» и «С».

1
IntStream chars = "ABC".chars();

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

1
IntStream randomInts = new Random().ints();

Stream Array

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

1
2
String[] array = {"Monkey", "Lion", "Giraffe", "Lemur"};
Stream <String> stream2 = Stream.of(array);
1
Stream <String> stream = Stream.of("Monkey", "Lion", "Giraffe", "Lemur");

Поток из коллекции

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

1
2
List <String> list = Arrays.asList("Monkey", "Lion", "Giraffe", "Lemur");
Stream <String> streamFromList = list.stream();
1
2
Set set = new HashSet<>(list);
Stream <String> streamFromSet = set.stream();

Поток из текстового файла

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

1
Stream <String> lines = Files.lines(Paths.get("file.txt"));

Упражнение

Теперь, когда мы ознакомили вас с некоторыми способами создания Stream, мы рекомендуем вам клонировать этот репозиторий GitHub и начать практиковать. Содержание статьи будет достаточно, чтобы решить первый блок, который называется Create. Интерфейс Unit1Create содержит JavaDocs, который описывает предполагаемую реализацию методов в Unit1MyCreate .

1
2
3
4
5
6
7
8
9
public interface Unit1Create {
 /**
  * Creates a new Stream of String objects that contains
  * the elements "A", "B" and "C" in order.
  *
  * @return a new Stream of String objects that contains
  *   the elements "A", "B" and "C" in order
  */
  Stream <String> newStreamOfAToC();

Предоставленные тесты (например, Unit1MyCreateTest) будут действовать как инструмент автоматической оценки, сообщая вам, было ли ваше решение верным или нет.

Если вы еще этого не сделали, продолжайте и решите рабочие элементы в классе Unit1MyCreate. «Должен поймать их всех».

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

Смотрите оригинальную статью здесь: Станьте Мастером Java Streams — Часть 1. Создание потоков

Мнения, высказанные участниками Java Code Geeks, являются их собственными.