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

Stream API

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

Що таке Stream API?

Stream API – це новий спосіб працювати зі структурами даних у функціональному стилі. Stream (потік) API (опис способів, якими одна комп'ютерна програма може взаємодіяти з іншою програмою) - це по суті потік даних. Сам термін "потік" досить розмитий у програмуванні загалом і Java зокрема.
Stream API - 1
З появою Java 8 Stream API дозволило програмістам писати істотно коротше те, що раніше займало багато рядків коду, а саме спростити роботу з наборами даних, зокрема, спростити операції фільтрації, сортування та інші маніпуляції з даними. Якщо у вас проміжних операцій немає, часто можна і потрібно обійтися без стриму, інакше код буде складнішим, ніж без потоку.
Stream API - 2
З чого, власне, розпочати? Зі створення екземпляра 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 дозволяє скоротити кількість рядків коду. Приклад з потоком:
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) - перетворює всі елементи стриму в один об'єкт(порахувати суму всіх елементів, або знайти мінімальний елемент), спочатку береться об'єкт 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()- Збирає елементи в безліч:

    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)- Збирає елементи в один рядок. Додатково можна вказати роздільник, а також префікс та суфікс для всієї послідовності:

    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: не соромимося висипаємо лайками ^ : ^
Коментарі
  • популярні
  • нові
  • старі
Щоб залишити коментар, потрібно ввійти в систему
Для цієї сторінки немає коментарів.