Stream. Основы

Stream API это новый способ работать со структурами данных в функциональном стиле.

Устройство работы

Внутри stream содержит Spliterator, который так же как и Iterator позволяет итерироваться по объектам, и к тому же содержит методы для возможности разбиения одного Spliterator на несколько для возможности паралельной обработки лежащих в них сущностей.

Конвеерные операции создают новый стрим, в котором Spliterator оборачивает Spliterator предыдущего стрима, определенным образом, при этом новый Spliterator будет иметь (в зависимости от операции) новые характеристики (Изменение свойств стримов (Сплитераторов)).

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

Пример однопроходности

Stream<?> stream = MyStream();

Stream<?> stream2 = stream.map(Element::getField1);

// Ругнеться!
Stream<?> stream3 = stream.map(Element::getField2);

stream2.collect(...)

// Ругнеться!
stream2.collect(...)

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

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);

Изменение свойств стримов (Сплитераторов)

Immutable

DataSource не меняет свое состояние в процессе работы со стримом.
т.е. никакой другой поток в тот-же момент когда происходит обработка dataSource в стриме не внесет никаких изменений.

Concurrent

DataSource может изменяться в процессе работы стрима извне. Противоположно Immutable.

Distinct

На текущей стадии стрима (после выполнения одной из промеэуточных операций или при создании стрима) все элементы различные.

map, flatMap - снимает характеристику
distinct - проставляет характеристику

NonNull

Гарантирует, что внутри стрима нет null елементов. Оптимизирует работу в отноении null-checking.

map, flatMap - снимает характеристику

Ordered

Гарантирует упорядоченный набор (Не отсортированный, а лишь важность порядка, т.е. последовательность элементов). Например dataSource:List - гарантирует порядок, а dataSource:Set - не гарантирует.

unordered - снимает характеристику
sorted - проставляет характеристику

Sorted

Гарантирует отсортированность по какому либо признаку.

map, flatMap - снимает характеристику

Sized

Показывает знает ли стрим (Spliterator) свой размер.

filter, flatMap, distinct - снимает характеристику
limit - проставляет характеристику

Subsized

В паралельных стримах кусочки будут знать свой размер. Т.е. Spliterator делит на определенные в отношении размера последовательности. Несбалансированное дерево например будет обладать характеристикой Sized, но не будет обладать характеристикой Subsized.

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

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);

Методы создания стримов

Классический: Создание стрима из коллекции 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.generate(выражение_генерации)

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