Константин
36 уровень

Stream API

Статья из группы Random
участников
Stream API - 1

Что такое Stream API?

Stream API — это новый способ работать со структурами данных в функциональном стиле. Stream (поток) API (описание способов, которыми одна компьютерная программа может взаимодействовать с другой программой) — это по своей сути поток данных. Сам термин "поток" довольно размыт в программировании в целом и в Java в частности.
Stream API - 1
С появлением Java 8 Stream API позволило программистам писать существенно короче то, что раньше занимало много строк кода, а именно — упростить работу с наборами данных, в частности, упростить операции фильтрации, сортировки и другие манипуляции с данными. Если у вас промежуточных операций нет, часто можно и нужно обойтись без стрима, иначе код будет сложнее чем без потока.
Stream API - 2
C чего, собственно, начать? С создания экземпляра Stream, который опирается на нужную нам коллекцию, массив или метод их и откуда соответственно будут браться данные:
List<String> list = new ArrayList<String>();
       list.add("One");
       list.add("Two");
       list.add("Three");
       list.add("Four");
       list.add("Five");
       list.add("Six");
       list.add("Seven");
       list.add("Eight");
       list.add("Nine");
       list.add("Ten");
       Stream stream = list.stream();
Как говорилось выше, Stream API позволяет сократить количество строк кода. Пример c потоком:
IntStream.of(50, 60, 70, 80, 90, 100, 110, 120).filter(x -> x < 90).map(x -> x + 10)
.limit(3).forEach(System.out::print);
Пример без потока:
int[] arr = {50, 60, 70, 80, 90, 100, 110, 120
	int count = 0;
	for (int x : arr) {
	    if (x >= 90) continue;
	    x += 10;
	    count++;
	    if (count > 3) break;
	    System.out.print(x);
	}
Возможные способы создания Stream:
Stream API - 3
  • Пустой стрим: Stream.empty()
  • Стрим из List: list.stream()
  • Стрим из Map: map.entrySet().stream()
  • Стрим из массива: Arrays.stream(array)
  • Стрим из указанных элементов: Stream.of("1", "2", "3")
Далее, есть такое понятие как операторы (по сути методы класса Stream) Операторы можно разделить на две группы:
  • Промежуточные (“intermediate”, ещё называют “lazy”) — обрабатывают поступающие элементы и возвращают стрим. Промежуточных операторов в цепочке обработки элементов может быть много.
  • Терминальные (“terminal”, ещё называют “eager”) — обрабатывают элементы и завершают работу стрима, так что терминальный оператор в цепочке может быть только один.
Пример:
1.List<String> list = new ArrayList<String>();
2.list.add("One");11.list.add("Ten");
12.Stream stream = list.stream();
13.stream.filter(x-> x.toString().length() == 3).forEach(System.out::println);
Что здесь происходит:
  • 1 — создаём список list;
  • 2-11 — заполняем его тестовыми данными;
  • 12 — создаём обьект Stream;
  • 13 — метод filter (фильтр) — промежуточный оператор, x приравнивается к одному элементу коллекции для перебора (как при for each) и после -> мы указываем как фильтруется наша коллекция и так как это промежуточный оператор, отфильтрованная коллекция идёт дальше в метод forEach который в свою очередь является терминальным (конечным) аналогом перебора for each (Выражение System.out::println сокращенно от: x-> System.out.println(x)), которое в свою очередь проходит по всем элементам переданной ему коллекции и выводит её)
Stream API - 5
Важные моменты:
  • Обработка не начнётся до тех пор, пока не будет вызван терминальный оператор. list.stream().filter(s -> s > 5) (не возьмёт ни единого элемента из списка);
  • Экземпляр, стрима нельзя использовать более одного раза =( ;
  • Stream API - 6

    Поэтому каждый раз новый:

    list.stream().filter(x-> x.toString().length() == 3).forEach(System.out::println);
    list.stream().forEach(x -> System.out.println(x));
  • промежуточных операторов вызванных на одном стриме может быть множество, в то время терминальный оператор только один:

    stream.filter(x-> x.toString().length() == 3).map(x -> x + " - the length of the letters is three").forEach(x -> System.out.println(x));
Далее давайте рассмотрим некоторые промежуточные операторы:
Stream API - 7
  • filter(Predicate predicate) фильтрует стрим, пропуская только те элементы, что проходят по условию (Predicate встроенный функциональный интерфейс, добавленный в Java SE 8 в пакет java.util.function. Проверяет значение на “true” и “false”);
  • map(Function mapper) даёт возможность создать функию с помощью которой мы будем изменять каждый элемент и пропускать его дальше (Функциональный интерфейс Function<T,R> представляет функцию перехода от объекта типа T к объекту типа R)
  • flatMap(Function<T, Stream<R>> mapper) — как и в случае с map, служат для преобразования в примитивный стрим.
При работе например с массивом стримов (массивов, списков и так далее) преобразует их в один стрим (массив,список и так далее [stream1,stream2,stream3,stream4] => stream:
String[] array = {"Java", "Ruuuuussshhh"};
Stream<String> streamOfArray = Arrays.stream(array);
streamOfArray.map(s->s.split("")) //Преобразование слова в массив букв
        .flatMap(Arrays::stream).distinct() //выравнивает каждый сгенерированный поток в один поток
        .collect(Collectors.toList()).forEach(System.out::println);
В то время когда map преобразует в список потоков (точнее <Stream> потоков) [stream1,stream2,stream3,stream4] =>Stream.of(stream1,stream2,stream3,stream4):
String[] array = {"Java", "Ruuuuussshhh"};
Stream<String> streamOfArray = Arrays.stream(array);
streamOfArray.map(s->s.split("")) //Преобразование слова в массив букв
        .map(Arrays::stream).distinct() //Сделать массив в отдельный поток
        .collect(Collectors.toList()).forEach(System.out::println);
Ещё одно отличие в сравнении с map, можно преобразовать один элемент в ноль, один или множество других. Для того, чтобы один элемент преобразовать в ноль элементов, нужно вернуть null, либо пустой стрим. Чтобы преобразовать в один элемент, нужно вернуть стрим из одного элемента, например, через Stream.of(x). Для возвращения нескольких элементов, можно любыми способами создать стрим с этими элементами. Тот же метод flatMap, но для Double, Integer и Long:
  • flatMapToDouble(Function mapper)
  • flatMapToInt(Function mapper)
  • flatMapToLong(Function mapper)
И ещё пример для сравнения, flatMap:
Stream.of(2, 3, 0, 1, 3)
        .flatMapToInt(x -> IntStream.range(0, x))
        .forEach(System.out::print);// 010120012
  • IntStream.range(0,x) – выдаёт на поток элементов с 0 (включительно) по x (не включительно);

    map:

    Stream.of(2, 3, 0, 1, 3)
            .map(x -> IntStream.range(0, x))
            .forEach(System.out::print);//перечень стримов(потоков);
  • limit(long maxSize) – ограничивает стрим по количеству элементов:

    stream.limit(5).forEach(x -> System.out.println(x));
  • skip(long n) – пропускаем n элементов:

    stream.skip(3).forEach(x -> System.out.println(x));
  • sorted()

  • sorted(Comparator comparator) – сортирует стрим (сортировка как у TreeMap):

    stream.sorted().forEach(x -> System.out.println(x));
  • distinct() — проверяет стрим на уникальность элементов(убирает повторы элементов);

  • dropWhile(Predicate predicate) — пропускает элементы которые удовлетворяют условию (появился в 9 java, Функциональный интерфейс Predicate<T> проверяет соблюдение некоторого условия. Если оно соблюдается, то возвращается значение true. В качестве параметра лямбда-выражение принимает объект типа T:

    Predicate<Integer> isPositive = x -> x > 0;
           System.out.println(isPositive.test(3)); // true
           System.out.println(isPositive.test(-9)); // false
Терминальные операторы:
Stream API - 8
  • forEach(Consumer action) – аналог for each (Consumer<T> выполняет некоторое действие над объектом типа T, при этом ничего не возвращая);

  • count() – возвращает количество елементов стрима:

    System.out.println(stream.count());

  • collect(Collector collector) – метод собирает все элементы в список, множество или другую коллекцию, сгруппировывает элементы по какому-нибудь критерию, объединяет всё в строку и т.д.:

    List<String> list = Stream.of(One,Two,Three).collect(Collectors.toList());
  • collect(Supplier supplier, BiConsumer accumulator, BiConsumer combiner) — тот же, что и collect(collector), только параметры разбиты для удобства (supplier поставляет новые объекты (контейнеры), например new ArrayList(), accumulator добавляет элемент в контейнер, combiner объединяет части стрима воедино);

  • reduce(T identity, BinaryOperator accumulator) — преобразовывает все элементы стрима в один объект(посчитать сумму всех элементов, либо найти минимальный элемент), cперва берётся объект identity и первый элемент стрима, применяется функция accumulator и identity становится её результатом. Затем всё продолжается для остальных элементов.

    int sum = Stream.of(1, 2, 3, 4, 5).reduce(10, (acc, x) -> acc + x);// = 25
  • reduce(BinaryOperator accumulator) — такой же метод как и выше но отсутсвует начальный identity, им служит первый элемент стрима

    Optional min(Comparator comparator)
    Optional max(Comparator comparator) ищет минимальный/максимальный элемент, основываясь на переданном компараторе;

  • findFirst() – вытаскивает первый элемент стрима:

    Stream.of(1, 2, 3, 4, 9).findFirst();
  • allMatch(Predicate predicate) — возвращает true, если все элементы стрима удовлетворяют условию. Если встречается какой-либо элемент, для которого результат вызова функции-предиката будет false, то оператор перестаёт просматривать элементы и возвращает false:

    Stream.of(1, 2, 3, 4, 9).allMatch(x -> x <= 7);//false
  • anyMatch(Predicate predicate) — вернет true, если хотя бы один элемент стрима удовлетворяет условию predicate:

    Stream.of(1, 2, 3, 4, 9).anyMatch(x -> x >= 7);//true
  • noneMatch(Predicate predicate) — вернёт true, если, пройдя все элементы стрима, ни один не удовлетворил условию predicate:

    Stream.of(1, 2, 3, 4, 9).noneMatch(x -> x >= 7);//false
И хотелось бы напоследок просмотреть некоторые методы Collectors:
  • toList() — собирает элементы в List:

    List<Integer> list = Stream.of(99, 2, 3).collect(Collectors.toList());
  • toSet() — cобирает элементы в множество:

    Set<Integer> set = Stream.of(99, 2, 3).collect(Collectors.toSet());
  • counting() — Подсчитывает количество элементов:

    Long count = Stream.of("1", "2", "3", "4").collect(Collectors.counting());
  • joining()

  • joining(CharSequence delimiter)

  • joining(CharSequence delimiter, CharSequence prefix, CharSequence suffix) — cобирает элементы в одну строку. Дополнительно можно указать разделитель, а также префикс и суффикс для всей последовательности:

    String a = Stream.of("s", "u" ,"p", "e", "r").collect(Collectors.joining());
           System.out.println(a); // super
    
           String b = Stream.of("s", "u", "p", "e", "r").collect(Collectors.joining("-"));
           System.out.println(b); // s-u-p-e-r
    
           String c = Stream.of("s", "u", "p", "e", "r").collect(Collectors.joining(" -> ", "[ ", " ]"));
           System.out.println(c);  // [ s -> u -> p -> e -> r ]
  • summingInt(ToIntFunction mapper)

  • summingLong(ToLongFunction mapper)

  • summingDouble(ToDoubleFunction mapper) — коллектор, который преобразовывает объекты в int/long/double и подсчитывает сумму.

Полезные ссылки: PS: не стесняемся сыпем лайками ^ : ^
Комментарии (48)
  • популярные
  • новые
  • старые
Для того, чтобы оставить комментарий Вы должны авторизоваться
Евгений N
Уровень 23
10 октября 2023, 07:25
мдя... "Stream API позволяет сократить количество строк кода. " но зачем? кто вспомнил молодость и память в 64К? или как "удобно" писать на Лисп 😢
Андрей
Уровень 2
23 августа 2023, 14:59
А кто нибудь знает как с помощью Stream из коллекции найти ближайшее значение к заданному
Kiske
Уровень 35
29 августа 2023, 18:36
Random random = new Random();
int x = 5;

Stream<Integer> stream = random.ints(10, -100, 100).boxed();
List<Integer> list = stream.collect(Collectors.toList());

System.out.println(list);

System.out.println(list.stream()
    .min(Comparator.comparingInt(val -> Math.abs(val - x)))
    .orElse(0));
нужный код - это
list.stream()
    .min(Comparator.comparingInt(val -> Math.abs(val - x)))
    .orElse(0)
тут список list задать нужный надо и поменять значение x. Ну и задать нужный компаратор для того типа, который нужно сравнить между собой.
Dmitry Student в Home
23 января 2023, 15:01
Про map и flatMap вроде много написано в статье, но ничего не понял. Так в чём разница между ними?
Дмитрий
Уровень 29
22 февраля 2022, 14:05
В предложении IntStream.range(0,x) – выдаёт на поток элементов с 0 (включительно) по x (не включительно); частица "по" уже сама по себе означает включительно. Поэтому контрастирует со словом в скобках ("не включительно"). Имеет смысл заменить "по" на "до". Последняя частица хотя и подразумевает сама по себе "не включая", но слово в скобках оставить можно.
Серж машинист в РЖД
2 марта 2022, 03:03
капец ты умный 😉
Don Keydick Багодел-быдлокодер
5 июля 2022, 19:42
Откройте форточку. Здесь стало душно.
Roma Ко
Уровень 17
23 января 2022, 18:35
норм
Artyom Lebedev
Уровень 47
21 ноября 2021, 09:37
BestVideoEver Вот лучшее объяснение лямбд и стримов, всё сразу встает на свои места.
prime
Уровень 42
21 ноября 2022, 08:14
спасибо, очень подробно, наконец-то стало понятно про лямбды и стримы
finnadzorservice
Уровень 36
18 октября 2021, 23:27
Афтар с описанием dropWhile(Predicate predicate) ошибся
hidden #2641196
Уровень 51
12 июля 2021, 18:25
Отличная статья!
Oleg Chilikin QA Automation Engineer
2 апреля 2021, 05:07
Шикарная статья!)