Потоки Java 14. Методы и операции фабрики потоков

Технически все они являются методами, принадлежащими интерфейсу Stream. Но некоторые из них создают объект Stream (мы называем их фабричные методы или просто методы; все они статические), а другие обрабатывают элементы испускаются созданным объектом Stream (мы называем их операциями; все они нестатичны).

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

Методы фабрики потоков

Ниже приведены фабричные методы интерфейса Stream:

  static Stream<T> generate(Supplier<T> s);
  static Stream<T> iterate(T seed, UnaryOperator<T> f);
  static Stream<T> iterate(T seed, Predicate<T> hasNext, 
                                    UnaryOperator<T> next);
  static Stream.Builder<T> builder();
  static Stream<T> of(T... values);
  static Stream<T> empty();
  static Stream<T> ofNullable(T t);
  static Stream<T> concat(Stream<T> a, Stream<T> b);

Первые пять мы уже обсуждали в этой серии:

static Stream‹T› generate(Supplier‹T› s);
static Stream‹T› iterate(T seed, UnaryOperator‹T› f);
static Stream‹T› iterate(T seed, Predicate‹T› hasNext, UnaryOperator‹T› next);

static Stream.Builder‹T› builder();
static Stream‹T› of(T… values);

Остальные три — empty(), ofNullable() и concat() — мы обсудим в следующей статье Фабрика потоков. методы.

Потоковая передача промежуточных операций

Ниже приведены промежуточные операции, которые сортируют, пропускают и фильтруют элементы, создаваемые объектом Stream:

  Stream<T> sorted();
  Stream<T> distinct(); 
  Stream<T> skip(long n);
  Stream<T> limit(long maxSize); 
  Stream<T> filter(Predicate<T> predicate); 
  Stream<T> sorted(Comparator<T> comparator);
  default Stream<T> dropWhile(Predicate<T> predicate);
  default Stream<T> takeWhile(Predicate<T> predicate);

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

  Stream<R> map(Function<T,R> mapper);
  IntStream mapToInt(ToIntFunction<T> mapper);
  LongStream mapToLong(ToLongFunction<T> mapper);
  DoubleStream mapToDouble(ToDoubleFunction<T> mapper);

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

  Stream<R> flatMap(Function<T, Stream<R>> mapper);
  IntStream flatMapToInt(Function<T,IntStream> mapper);
  LongStream flatMapToLong(Function<T,LongStream> mapper);
  DoubleStream flatMapToDouble(Function<T,DoubleStream> mapper);

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

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

Stream<T> peek(Consumer<T> action);

Откуда мы знаем, что эта операция не изменяет элементы потока? Он использует функцию Consumer для выполнения своей работы, а функция Consumer ничего не возвращает (void). Ниже приведен пример использования операции peek():

    List.of("1","2","3").stream()
        .map(s -> s + "a")
        .peek(System.out::println)
        .map(s -> s + "b")
        .forEach(System.out::println);

Вывод приведенного выше фрагмента кода выглядит следующим образом:

  1a
  1ab
  2a
  2ab
  3a
  3ab

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

Потоковые операции терминала

Ниже приведены все операции терминала Stream:

  long count();
  void forEach(Consumer<T> action);
  void forEachOrdered(Consumer<T> action);
  boolean allMatch(Predicate<T> predicate);
  boolean anyMatch(Predicate<T> predicate);
  boolean noneMatch(Predicate<T> predicate);
  Optional<T> findAny();
  Optional<T> findFirst();
  Optional<T> max(Comparator<T> comparator);
  Optional<T> min(Comparator<T> comparator);
  Object[] toArray();
  A[] toArray(IntFunction<A[]> generator);
  Optional<T> reduce(BinaryOperator<T> accumulator);
  T reduce(T identity, BinaryOperator<T> accumulator);
  U reduce(U identity, BiFunction<U,T,U> accumulator, 
                          BinaryOperator<U> combiner);
  R collect(Collector<T,A,R> collector);
  R collect(Supplier<R> supplier, BiConsumer<R,T> accumulator,
                                     BiConsumer<R,R> combiner);

Операция count() проста. Он возвращает количество элементов, испускаемых потоком. Естественно, имеет смысл использовать только с конечными потоками. В противном случае он никогда не возвращает никакого значения. Вот пример его использования:

  long c = List.of("a","b","c").stream().count();
  System.out.print(c);            //prints: 3

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

Операция forEachOrdered() полезна, когда обработка потока происходит параллельно (подробнее о параллельной обработке мы поговорим в одном из следующих постов). Чтобы продемонстрировать разницу между операциями forEach() и forEachOrdered(), воспользуемся ими для параллельной обработки одного и того же потока:

    List.of("1","2","3","4")
        .parallelStream()
        .forEach(System.out::print);        //prints: 3421
    List.of("1","2","3","4")
        .parallelStream()
        .forEachOrdered(System.out::print); //prints: 1234

Как видите, операция forEachOrdered() обрабатывает элементы потока в том порядке, в котором они были отправлены потоком, а операция forEach() обрабатывает их в том порядке, в котором получает элементы из подпотоков, обрабатываемых параллельно, поэтому порядок обработки может меняться при каждом запуске.

Другие операции с терминалом мы обсудим в следующих постах.

В следующем посте мы поговорим о трех фабричных методах — empty(), ofNullable() и concat().

См. также другие сообщения о потоках Java 8.