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

Java Streams vs Vavr Streams

Полный обзор двух различных реализаций потока.

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

1. введение

В этой статье мы рассмотрим, как реализации Stream отличаются в Java и Var.

Эта статья предполагает знакомство с основами как Java Stream API, так и библиотеки Var .

2. Сравнение

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

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

Вот почему потоки Java поддерживаются экземплярами Spliterator – обновление до гораздо более старого Iterator , а реализация Vavr поддерживается вышеупомянутым Iterator (по крайней мере, в одной из последних реализаций).

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

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

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

3. Дополнительная Функциональность

Подход к работе с потоками и их элементами приводит к интересным различиям в способах работы с ними как в Java, так и в Vavra

3.1. Доступ к Случайным Элементам

Предоставление удобных API и методов доступа к элементам-это одна из областей, в которой Vavr действительно сияет над Java API. Например, Var имеет некоторые методы, которые обеспечивают случайный доступ к элементам:

  • get() обеспечивает индексный доступ к элементам потока.
  • indexOf() предоставляет ту же функциональность расположения индекса, что и в стандартном списке Java .
  • insert() предоставляет возможность добавить элемент в поток в заданной позиции.
  • intersperse() вставит предоставленный аргумент между всеми элементами потока.
  • find() найдет и вернет элемент из потока. Java предоставляет none Matched , который просто проверяет наличие элемента.
  • update() заменит элемент по заданному индексу. Это также принимает функцию для вычисления замены.
  • search () найдет элемент в отсортированном | потоке (несортированные потоки дадут неопределенный результат)

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

3.2. Параллелизм и параллельная модификация

В то время как потоки Vavr изначально не поддерживают параллелизм, как метод Java parallel () , существует метод toJavaParallelStream |, который обеспечивает распараллеленную копию исходного потока Vavr на основе Java.

Область относительной слабости в потоках Var основана на принципе Невмешательства .

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

List intList = new ArrayList<>();
intList.add(1);
intList.add(2);
intList.add(3);
Stream intStream = intList.stream(); //form the stream
intList.add(5); //modify underlying list
intStream.forEach(i -> System.out.println("In a Java stream: " + i)); 

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

in a Java stream: 1
in a Java stream: 2
in a Java stream: 3
in a Java stream: 5

Мы обнаруживаем, что поток Var этого не потерпит:

Stream vavrStream = Stream.ofAll(intList);
intList.add(5)
vavrStream.forEach(i -> System.out.println("in a Vavr Stream: " + i));

Что мы получаем:

Exception in thread "main" java.util.ConcurrentModificationException
  at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901)
  at java.util.ArrayList$Itr.next(ArrayList.java:851)
  at io.vavr.collection.StreamModule$StreamFactory.create(Stream.java:2078)

Потоки Var не “хорошо себя ведут” по стандартам Java. Var лучше работает с примитивными структурами данных резервного копирования:

int[] aStream = new int[]{1, 2, 4};
Stream wrapped = Stream.ofAll(aStream);

aStream[2] = 5;
wrapped.forEach(i -> System.out.println("Vavr looped " + i));

Даешь нам:

Vavr looped 1
Vavr looped 2
Vavr looped 5

3.3. Операции короткого замыкания и плоская карта()

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

Однако в JDK 8 и 9 есть ошибка , которая заставляет реализацию flatMap нарушать этот контракт и нетерпеливо оценивать его в сочетании с короткозамкнутыми промежуточными операциями, такими как findFirst или limit .

Простой пример:

Stream.of(42)
  .flatMap(i -> Stream.generate(() -> { 
      System.out.println("nested call"); 
      return 42; 
  }))
  .findAny();

В приведенном выше фрагменте мы никогда не получим результат от findAny , потому что flatMap будет оцениваться с нетерпением, а не просто брать один элемент из вложенного потока.

Исправление этой ошибки было предоставлено в Java 10.

Vavr’s flatMap не имеет такой же проблемы, и функционально аналогичная операция завершается в O(1):

Stream.of(42)
  .flatMap(i -> Stream.continually(() -> { 
      System.out.println("nested call"); 
      return 42; 
  }))
  .get(0);

3.4. Основные функциональные возможности Vavr

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

  • zip() соединяет элементы в потоке с элементами из поставляемого итерабельного файла. Эта операция раньше поддерживалась в JDK-8, но с тех пор была удалена после сборки-93 partition()
  • разделит содержимое потока на два потока с заданным предикатом. permutation()
  • как указано, вычислит перестановку (все возможные уникальные порядки) элементов потока. combinations()
  • дает комбинацию (т. е. возможный выбор элементов) потока. groupBy
  • вернет Карту потоков, содержащих элементы из исходного потока, классифицированные поставляемым классификатором.
  • |/distinct метод в Var улучшает версию Java, предоставляя вариант, который принимает компаратор лямбда-выражение.

В то время как поддержка расширенной функциональности несколько не вдохновляет потоки Java SE, Expression Language 3.0 странно обеспечивает поддержку гораздо большей функциональности, чем стандартные потоки JDK.

4. Манипуляция потоком

Vavr позволяет напрямую манипулировать содержимым потока:

  • Вставить в существующий поток Var
Stream vavredStream = Stream.of("foo", "bar", "baz");
vavredStream.forEach(item -> System.out.println("List items: " + item));
Stream vavredStream2 = vavredStream.insert(2, "buzz");
vavredStream2.forEach(item -> System.out.println("List items: " + item));
  • Удаление элемента из потока
Stream removed = inserted.remove("buzz");
  • Операции на основе очередей

Поскольку поток Vavr поддерживается очередью, он обеспечивает операции постоянного времени prepend и append .

Однако изменения, внесенные в поток Var, не распространяются обратно на источник данных, из которого был создан поток.

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

У Vavr и Java есть свои сильные стороны, и мы продемонстрировали приверженность каждой библиотеки своим целям проектирования – Java к дешевому параллелизму и Vavr к удобным потоковым операциям.

Благодаря поддержке Vavr для преобразования туда и обратно между собственным потоком и потоком Java можно получить преимущества обеих библиотек в одном проекте без больших накладных расходов.

Исходный код этого учебника доступен на Github .