Тема 22. Stream - BelyiZ/JavaCourses GitHub Wiki
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")
- Классический: Создание стрима из коллекции
collection.stream()
Пример:
Collection<String> collection = Arrays.asList("a1", "a2", "a3");
Stream<String> streamFromCollection = collection.stream();- Создание стрима из значений
Stream.of(значение1,… значениеN)
Пример:
Stream<String> streamFromValues = Stream.of("a1", "a2", "a3");- Создание стрима из массива
Arrays.stream(массив)
Пример:
String[] array = {"a1","a2","a3"};
Stream<String> streamFromArrays = Arrays.stream(array);- Создание стрима из файла (каждая строка в файле будет отдельным элементом в стриме)
Files.lines(путь_к_файлу)
Пример:
Stream<String> streamFromFiles = Files.lines(Paths.get("file.txt"))- Создание стрима из строки
«строка».chars()
Пример:
IntStream streamFromString = "123".chars()- С помощью
Stream.builder
Пример:
Stream.builder().add(...)....build()
Stream.builder().add("a1").add("a2").add("a3").build()- Создание параллельного стрима
collection.parallelStream()
Пример:
Stream<String> stream = collection.parallelStream();- Создание бесконечных стрима с помощью
Stream.iterate
Пример:
Stream.iterate(начальное_условие, выражение_генерации)
Stream<Integer> streamFromIterate = Stream.iterate(1, n -> n + 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».
Java Stream API предлагает два вида методов:
- Конвейерные (“intermediate”, ещё называют “lazy”) — обрабатывают поступающие элементы и возвращают другой stream, то есть работают как
builder. - Терминальные (“terminal”, ещё называют “eager”) — возвращают другой объект, такой как коллекция, примитивы, объекты,
Optionalи т.д. Они обрабатывают элементы и завершают работу стрима, поэтому терминальный оператор в цепочке может быть только один.
Общее правило: у Stream может быть сколько угодно вызовов конвейерных вызовов и в конце один терминальный, при этом все конвейерные методы выполняются лениво и пока не будет вызван терминальный метод никаких действий на самом деле не происходит.
В целом, этот механизм похож на конструирования SQL запросов, может быть сколько угодно вложенных Select и только один результат в итоге.
Пример: в выражении
collection.stream().filter((s) -> s.contains(«1»)).skip(2).findFirst() filter и skip — конвейерные, а findFirst — терминальный, он возвращает объект Optional и это заканчивает работу со Stream.
-
filterОтфильтровывает записи, возвращает только записи, соответствующие условию
Пример:
collection.stream().filter(«a1»::equals).count()-
skipПозволяет пропустить N первых элементов
Пример:
collection.stream().skip(collection.size() — 1).findFirst().orElse(«1»)-
distinctВозвращает стрим без дубликатов (для методаequals)
Пример:
collection.stream().distinct().collect(Collectors.toList())-
mapПреобразует каждый элемент стрима
Пример:
collection.stream().map((s) -> s + "_1").collect(Collectors.toList())-
peekВозвращает тот же стрим, но применяет функцию к каждому элементу стрима
Пример:
collection.stream().map(String::toUpperCase).peek((e) -> System.out.print("," + e)).
collect(Collectors.toList())-
limitПозволяет ограничить выборку определенным количеством первых элементов
Пример:
collection.stream().limit(2).collect(Collectors.toList())-
sortedПозволяет сортировать значения либо в натуральном порядке, либо задаваяComparator
Пример:
collection.stream().sorted().collect(Collectors.toList())-
mapToInt,mapToDouble,mapToLongАналог map, но возвращает числовой стрим (то есть стрим из числовых примитивов)
Пример:
collection.stream().mapToInt((s) -> Integer.parseInt(s)).toArray()-
flatMap,flatMapToInt,flatMapToDouble,flatMapToLongПохоже на map, но может создавать из одного элемента несколько
Пример:
collection.stream().flatMap((p) -> Arrays.asList(p.split(",")).stream()).toArray(String[]::new)-
findFirstВозвращает первый элемент из стрима (возвращаетOptional)
Пример:
collection.stream().findFirst().orElse(«1»)-
findAnyВозвращает любой подходящий элемент из стрима (возвращаетOptional)
Пример:
collection.stream().findAny().orElse(«1»)-
collectПредставление результатов в виде коллекций и других структур данных
Пример:
collection.stream().filter((s) -> s.contains(«1»)).collect(Collectors.toList())-
countВозвращает количество элементов в стриме
Пример:
collection.stream().filter(«a1»::equals).count()-
anyMatchВозвращает true, если условие выполняется хотя бы для одного элемента
Пример:
collection.stream().anyMatch(«a1»::equals)-
noneMatchВозвращает true, если условие не выполняется ни для одного элемента
Пример:
collection.stream().noneMatch(«a8»::equals)-
allMatchВозвращает true, если условие выполняется для всех элементов
Пример:
collection.stream().allMatch((s) -> s.contains(«1»))-
minВозвращает минимальный элемент, в качестве условия использует компаратор
Пример:
collection.stream().min(String::compareTo).get()-
maxВозвращает максимальный элемент, в качестве условия использует компаратор
Пример:
collection.stream().max(String::compareTo).get()-
forEachПрименяет функцию к каждому объекту стрима, порядок при параллельном выполнении не гарантируется
Пример:
set.stream().forEach((p) -> p.append("_1"));-
forEachOrderedПрименяет функцию к каждому объекту стрима, сохранение порядка элементов гарантирует
Пример:
list.stream().forEachOrdered((p) -> p.append("_new"));-
toArrayВозвращает массив значений стрима
Пример:
collection.stream().map(String::toUpperCase).toArray(String[]::new);-
reduceПозволяет выполнять агрегатные функции на всей коллекцией и возвращать один результат
Пример:
collection.stream().reduce((s1, s2) -> s1 + s2).orElse(0)Методы findFirst, findAny, anyMatch это short-circuiting методы, то есть обход стримов организуется таким образом чтобы найти подходящий элемент максимально быстро, а не обходить весь изначальный стрим.
-
sumВозвращает сумму всех чисел
Пример:
collection.stream().mapToInt((s) -> Integer.parseInt(s)).sum()-
averageВозвращает среднее арифметическое всех чисел
Пример:
collection.stream().mapToInt((s) -> Integer.parseInt(s)).average()-
mapToObjПреобразует числовой стрим обратно в объектный
Пример:
intStream.mapToObj((id) -> new Key(id)).toArray()-
isParallelУзнать является ли стрим параллельным -
parallelВернуть параллельный стрим, если стрим уже параллельный, то может вернуть самого себя -
sequentialВернуть последовательный стрим, если стрим уже последовательный, то может вернуть самого себя
С помощью, методов parallel и sequential можно определять какие операции могут быть параллельными, а какие только последовательными.
Так же из любого последовательного стрима можно сделать параллельный и наоборот: collection.stream(). peek(...). // операция последовательна parallel(). map(...). // операция может выполняться параллельно, sequential(). reduce(...) // операция снова последовательна
Не рекомендуется использовать параллельные стримы для сколько-нибудь долгих операций (получение данных из базы, сетевых соединений), так как все параллельные стримы работают c одним пулом fork/join и такие долгие операции могут остановить работу всех параллельных стримов в JVM из-за того отсутствия доступных потоков в пуле, т.е. параллельные стримы стоит использовать лишь для коротких операций, где счет идет на миллисекунды, но не для тех где счет может идти на секунды и минуты.

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));
}- https://www.oracle.com/technical-resources/articles/java/ma14-java-se-8-streams.html
- https://www.oracle.com/technical-resources/articles/java/architect-streams-pt2.html
- http://prologistic.com.ua/polnoe-rukovodstvo-po-java-8-stream.html
Тема 21. Указатели на методы | Оглавление | Тема 23. Spliterator