Рубрики
Без рубрики

Java 8 и бесконечные потоки

Краткое и практическое руководство по работе с бесконечными потоками в Java 8.

Автор оригинала: baeldung.

1. Обзор

В этой статье мы рассмотрим файл java.util.Stream API, и мы увидим, как мы можем использовать эту конструкцию для работы с бесконечным потоком данных/элементов.

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

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

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

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

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

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

Промежуточные операции не выполняются, вызывается некоторая терминальная операция.

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

  • фильтр()
  • карта()
  • Плоская карта()
  • отчетливый()
  • отсортированный()
  • заглядывать()
  • предел()
  • пропускать()

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

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

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

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

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

Операции терминала могут пересекать поток для получения результата или побочного эффекта.

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

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

  • инструкция foreach()
  • По порядку()
  • toArray()
  • уменьшить()
  • собирать()
  • минута()
  • максимум()
  • считать()
  • anyMatch()
  • allMatch()
  • ни одного совпадения()
  • findFirst()
  • найти Любую()

Каждая из этих операций вызовет выполнение всех промежуточных операций.

3. Бесконечные потоки

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

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

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

// given
Stream infiniteStream = Stream.iterate(0, i -> i + 2);

// when
List collect = infiniteStream
  .limit(10)
  .collect(Collectors.toList());

// then
assertEquals(collect, Arrays.asList(0, 2, 4, 6, 8, 10, 12, 14, 16, 18));

Мы создали бесконечный поток, используя метод iterative () . Затем мы вызвали преобразование limit() и операцию collect() terminal. Тогда в нашем результирующем Списке у нас будут первые 10 элементов бесконечной последовательности из-за лени потока .

4. Бесконечный поток элементов пользовательского типа

Допустим, мы хотим создать бесконечный поток случайных UUID .

Первым шагом к достижению этой цели с помощью Stream API является создание Поставщика этих случайных значений:

Supplier randomUUIDSupplier = UUID::randomUUID;

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

Stream infiniteStreamOfRandomUUID = Stream.generate(randomUUIDSupplier);

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

List randomInts = infiniteStreamOfRandomUUID
  .skip(10)
  .limit(10)
  .collect(Collectors.toList());

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

6. Делай-Пока – Потоковый путь

Допустим, у нас есть простой цикл do..while в нашем коде:

int i = 0;
while (i < 10) {
    System.out.println(i);
    i++;
}

Мы печатаем i счетчик десять раз. Мы можем ожидать, что такая конструкция может быть легко написана с помощью Stream API, и в идеале у нас был бы метод do While() в потоке.

К сожалению, в потоке нет такого метода, и когда мы хотим достичь функциональности, аналогичной стандартному циклу do-while , нам нужно использовать метод limit() :

Stream integers = Stream
  .iterate(0, i -> i + 1);
integers
  .limit(10)
  .forEach(System.out::println);

Мы достигли такой же функциональности, как императивный цикл while с меньшим количеством кода, но вызов функции limit() не так описателен, как если бы у нас был метод doWhile() для объекта Stream .

5. Заключение

В этой статье объясняется, как мы можем использовать API Stream для создания бесконечных потоков. Они, при использовании вместе с преобразованиями, такими как limit ()– , могут значительно облегчить понимание и реализацию некоторых сценариев.

Код, поддерживающий все эти примеры, можно найти в проекте GitHub – это проект Maven, поэтому его должно быть легко импортировать и запускать как есть.