Тема 22. Stream
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
Нашел ошибку или есть что добавить? Обязательно напиши мне об этом, рад любой обратной связи. А лучше сразу закидывай пулл-реквест!