Skip to content

Тема 22. Stream

Alesey edited this page Nov 15, 2022 · 6 revisions

Содержание:

  1. Обзор функциональности Stream
    1. Операторы (Методы класса Stream)
      1. Конвейерные методы работы со Stream
      2. Терминальные методы работы со Stream
      3. Методы у числовых Stream
      4. Особенные методы Stream
  2. Map/reduce
  3. Список литературы/курсов

Stream API — это способ работать со структурами данных Java, чаще всего коллекциями, в стиле функциональных языков программирования. — это объект для универсальной работы с данными. И это вовсе не какая-то новая структура данных, он использует существующие коллекции для получения новых элементов. То есть это объект для универсальной работы с данными. Он использует существующие коллекции для получения новых элементов. Затем к данным применяются методы. В интерфейсе Stream их множество. Каждый выполняет одну из типичных операций с коллекцией: отсортировать, перегруппировать, отфильтровать.

Stream API – это компонент, способный выполнять внутреннюю итерацию своих элементов, то есть он может выполнять итерацию своих элементов сам. Его можно воспринимать как цепочку вызовов методов — как о конвейере. Каждый промежуточный метод получает на вход результат выполнения с предыдущего этапа (стрим), отвечает только за свою часть работы и возвращает стрим. Последний (терминальный) метод либо не возвращает значения (void), либо возвращает результат иного, нежели стрим, типа.

Stream API не связан с Java InputStream и Java OutputStream Java IO. InputStream и OutputStream

Преимущества:

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

Дополнительные преимущества:

  • Стримы поддерживают один из основных принципов хорошего проектирования — слабую связанность (low coupling). Чем меньше класс знает про другие классы — тем лучше. Алгоритму сортировки не должно быть важно, что конкретно он сортирует. Это и делают стримы.
  • С помощью стримов операции с коллекциями проще распараллелить: в императивном подходе для этого бы понадобился минимум ещё один цикл.
  • Стримы позволяют уменьшить число побочных эффектов: методы Stream API не меняют исходные коллекции.
  • Со Stream API лаконично записываются сложные алгоритмы обработки данных.

Пустой стрим: Stream.empty() Стрим из List: list.stream() Стрим из Map: map.entrySet().stream() Стрим из массива: Arrays.stream(array) Стрим из указанных элементов: Stream.of("1", "2", "3")

Обзор функциональности

  1. Классический: Создание стрима из коллекции collection.stream()

Пример:

Collection<String> collection = Arrays.asList("a1", "a2", "a3");
Stream<String> streamFromCollection = collection.stream();
  1. Создание стрима из значений Stream.of(значение1,… значениеN)

Пример:

Stream<String> streamFromValues = Stream.of("a1", "a2", "a3");
  1. Создание стрима из массива Arrays.stream(массив)

Пример:

String[] array = {"a1","a2","a3"};   
Stream<String> streamFromArrays = Arrays.stream(array);
  1. Создание стрима из файла (каждая строка в файле будет отдельным элементом в стриме) Files.lines(путь_к_файлу)

Пример:

Stream<String> streamFromFiles = Files.lines(Paths.get("file.txt"))
  1. Создание стрима из строки «строка».chars()

Пример:

 IntStream streamFromString = "123".chars()
  1. С помощью Stream.builder

Пример:

Stream.builder().add(...)....build()
Stream.builder().add("a1").add("a2").add("a3").build()
  1. Создание параллельного стрима collection.parallelStream()

Пример:

Stream<String> stream = collection.parallelStream();
  1. Создание бесконечных стрима с помощью Stream.iterate

Пример:

Stream.iterate(начальное_условие, выражение_генерации)
Stream<Integer> streamFromIterate = Stream.iterate(1, n -> n + 1)
  1. Создание бесконечных стрима с помощью Stream.generate(выражение_генерации)

Пример:

Stream<String> streamFromGenerate = Stream.generate(() -> "a1")

Последние два способа служат для генерации бесконечных Stream, в iterate задается начальное условие и выражение получение следующего значения из предыдущего, то естьStream.iterate(1, n -> n + 1) будет выдавать значения 1, 2, 3, 4,… N. Stream.generate служит для генерации константных и случайных значений, то есть выдает значения соответствующие выражению, в данном примере, будет выдавать бесконечное количество значений «a1».

Операторы (Методы класса Stream)

Java Stream API предлагает два вида методов:

  1. Конвейерные (“intermediate”, ещё называют “lazy”) — обрабатывают поступающие элементы и возвращают другой stream, то есть работают как builder.
  2. Терминальные (“terminal”, ещё называют “eager”) — возвращают другой объект, такой как коллекция, примитивы, объекты, Optional и т.д. Они обрабатывают элементы и завершают работу стрима, поэтому терминальный оператор в цепочке может быть только один.

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

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

Пример: в выражении

collection.stream().filter((s) -> s.contains1»)).skip(2).findFirst() 

filter и skip — конвейерные, а findFirst — терминальный, он возвращает объект Optional и это заканчивает работу со Stream.

Конвейерные методы работы со Stream

  1. filter Отфильтровывает записи, возвращает только записи, соответствующие условию

Пример:

collection.stream().filtera1»::equals).count()
  1. skip Позволяет пропустить N первых элементов

Пример:

collection.stream().skip(collection.size() — 1).findFirst().orElse1»)
  1. distinct Возвращает стрим без дубликатов (для метода equals)

Пример:

collection.stream().distinct().collect(Collectors.toList())
  1. map Преобразует каждый элемент стрима

Пример:

collection.stream().map((s) -> s + "_1").collect(Collectors.toList())
  1. peek Возвращает тот же стрим, но применяет функцию к каждому элементу стрима

Пример:

collection.stream().map(String::toUpperCase).peek((e) -> System.out.print("," + e)).
collect(Collectors.toList())
  1. limit Позволяет ограничить выборку определенным количеством первых элементов

Пример:

collection.stream().limit(2).collect(Collectors.toList())
  1. sorted Позволяет сортировать значения либо в натуральном порядке, либо задавая Comparator

Пример:

collection.stream().sorted().collect(Collectors.toList())
  1. mapToInt, mapToDouble, mapToLong Аналог map, но возвращает числовой стрим (то есть стрим из числовых примитивов)

Пример:

collection.stream().mapToInt((s) -> Integer.parseInt(s)).toArray()
  1. flatMap, flatMapToInt, flatMapToDouble, flatMapToLong Похоже на map, но может создавать из одного элемента несколько

Пример:

collection.stream().flatMap((p) -> Arrays.asList(p.split(",")).stream()).toArray(String[]::new)

Терминальные методы работы со Stream

  1. findFirst Возвращает первый элемент из стрима (возвращает Optional)

Пример:

collection.stream().findFirst().orElse1»)
  1. findAny Возвращает любой подходящий элемент из стрима (возвращает Optional)

Пример:

collection.stream().findAny().orElse1»)
  1. collect Представление результатов в виде коллекций и других структур данных

Пример:

collection.stream().filter((s) -> s.contains1»)).collect(Collectors.toList())
  1. count Возвращает количество элементов в стриме

Пример:

collection.stream().filtera1»::equals).count()
  1. anyMatch Возвращает true, если условие выполняется хотя бы для одного элемента

Пример:

collection.stream().anyMatcha1»::equals)
  1. noneMatch Возвращает true, если условие не выполняется ни для одного элемента

Пример:

collection.stream().noneMatcha8»::equals)
  1. allMatch Возвращает true, если условие выполняется для всех элементов

Пример:

collection.stream().allMatch((s) -> s.contains1»))
  1. min Возвращает минимальный элемент, в качестве условия использует компаратор

Пример:

collection.stream().min(String::compareTo).get()
  1. max Возвращает максимальный элемент, в качестве условия использует компаратор

Пример:

collection.stream().max(String::compareTo).get()
  1. forEach Применяет функцию к каждому объекту стрима, порядок при параллельном выполнении не гарантируется

Пример:

set.stream().forEach((p) -> p.append("_1"));
  1. forEachOrdered Применяет функцию к каждому объекту стрима, сохранение порядка элементов гарантирует

Пример:

list.stream().forEachOrdered((p) -> p.append("_new"));
  1. toArray Возвращает массив значений стрима

Пример:

collection.stream().map(String::toUpperCase).toArray(String[]::new);
  1. reduce Позволяет выполнять агрегатные функции на всей коллекцией и возвращать один результат

Пример:

collection.stream().reduce((s1, s2) -> s1 + s2).orElse(0)

Методы findFirst, findAny, anyMatch это short-circuiting методы, то есть обход стримов организуется таким образом чтобы найти подходящий элемент максимально быстро, а не обходить весь изначальный стрим.

Методы у числовых Stream

  1. sum Возвращает сумму всех чисел

Пример:

collection.stream().mapToInt((s) -> Integer.parseInt(s)).sum()
  1. average Возвращает среднее арифметическое всех чисел

Пример:

collection.stream().mapToInt((s) -> Integer.parseInt(s)).average()
  1. mapToObj Преобразует числовой стрим обратно в объектный

Пример:

intStream.mapToObj((id) -> new Key(id)).toArray()

Особенные методы Stream

  1. isParallel Узнать является ли стрим параллельным
  2. parallel Вернуть параллельный стрим, если стрим уже параллельный, то может вернуть самого себя
  3. sequential Вернуть последовательный стрим, если стрим уже последовательный, то может вернуть самого себя

С помощью, методов parallel и sequential можно определять какие операции могут быть параллельными, а какие только последовательными.

Так же из любого последовательного стрима можно сделать параллельный и наоборот: collection.stream(). peek(...). // операция последовательна parallel(). map(...). // операция может выполняться параллельно, sequential(). reduce(...) // операция снова последовательна

Не рекомендуется использовать параллельные стримы для сколько-нибудь долгих операций (получение данных из базы, сетевых соединений), так как все параллельные стримы работают c одним пулом fork/join и такие долгие операции могут остановить работу всех параллельных стримов в JVM из-за того отсутствия доступных потоков в пуле, т.е. параллельные стримы стоит использовать лишь для коротких операций, где счет идет на миллисекунды, но не для тех где счет может идти на секунды и минуты.

Map, reduce

Map/Reduce это очень простой шаблон проектирования, описывающий работу с наборами данных в два шага: на первом шаге выполняются (параллельные) операции над набором, на втором шаге результаты первого шага объединяются. Данный шаблон используется для сокращения потока. Сокращение потока - это операция, которая возвращает одно значение путем объединения элементов потока.

Интерфейс Stream определяет методы mapTo*(), которые возвращают особые реализации Stream, имеющие методы average(),sum(),min(), max(), и count(), которые выполняют соответствующие арифметические действия над элементами набора данных для сокращения.

    public Double averageAge() {
        Integer currentYear = Calendar.getInstance().get(Calendar.YEAR);
        return data
                .stream()
                .mapToInt(u -> currentYear - u.getYob())
                .average()
                .getAsDouble();
    }

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

Пример:

Вычисление списка уникальных символов в логинах пользователей.

static String removeDuplicates(String s) {
    StringBuilder noDupes = new StringBuilder();
    for (int i = 0; i < s.length(); i++) {
        String si = s.substring(i, i + 1);
        if (noDupes.indexOf(si) == -1) {
            noDupes.append(si);
        }
    }
return noDupes.toString();
}
public String getCommonSymbols() {
    return data
        .stream()
        .map(User::getLogin)
        .reduce("", (p,c) -> removeDuplicates(p+c));
    }

Список-литературы/курсов

  1. https://www.oracle.com/technical-resources/articles/java/ma14-java-se-8-streams.html
  2. https://www.oracle.com/technical-resources/articles/java/architect-streams-pt2.html
  3. http://prologistic.com.ua/polnoe-rukovodstvo-po-java-8-stream.html

Тема 21. Указатели на методы | Оглавление | Тема 23. Spliterator

Clone this wiki locally