JavaRush /Курси /JAVA 25 SELF /Вступ до Stream API: навіщо потрібні потоки

Вступ до Stream API: навіщо потрібні потоки

JAVA 25 SELF
Рівень 30 , Лекція 0
Відкрита

1. Проблема імперативного підходу

Почнімо з класичного завдання. Припустімо, у нас є список користувачів:

List<User> users = ...; // вже заповнений

Припустімо, нам потрібно отримати список імен усіх повнолітніх користувачів (від 18 років). Як ми робили це раніше?

List<String> names = new ArrayList<>();
for (User user : users) {
    if (user.getAge() >= 18) {
        names.add(user.getName());
    }
}

На перший погляд усе просто, але зверніть увагу на «технічні» кроки: створити порожній список для результату, пройтися всіма елементами, перевірити умову, додати значення.

Якщо завдання ускладнюється — наприклад, потрібно отримати відсортований за абеткою список унікальних e‑mail‑адрес усіх користувачів, чиї імена починаються з літери «А», — код швидко розростається. Фільтрація, витягування полів, усунення дублікатів, сортування, можливо, перетворення формату e‑mail‑адрес тощо. У підсумку замість короткої, зрозумілої логіки ви отримуєте гору шаблонного коду, яку важко читати й підтримувати.

Мінуси такого підходу у двох словах: багато повторюваного коду, висока ймовірність припуститися помилки (забути про сортування або неправильно обробити дублікати) і складність комбінування операцій. Це і є імперативний стиль: ви крок за кроком описуєте компʼютеру, як виконувати завдання, а не що ви хочете отримати.

2. Stream API — декларативний стиль

Із виходом Java 8 з’явився Stream API — інструмент, що дає змогу працювати з колекціями у стилі «що потрібно зробити», а не «як саме це зробити». Такий підхід називають декларативним.

А що таке Stream взагалі?
Це не окрема колекція, а радше потік даних: послідовність елементів, що проходять через ланцюжок операцій — фільтрацію, перетворення, сортування, збирання результату тощо. Потік нічого не зберігає: він лише «протягує» дані через конвеєр. Операції можна поєднувати майже як кубики LEGO: зібрали ланцюжок і запустили.

Приклад:

List<String> names = users.stream()                // створюємо потік зі списку користувачів
    .filter(u -> u.getAge() >= 18)                 // залишаємо лише тих, кому виповнилося 18
    .map(User::getName)                            // перетворюємо User → String (беремо імʼя)
    .collect(Collectors.toList());                 // збираємо результат у список

І все — одним рядком ми повністю описали завдання: «візьміть користувачів, відфільтруйте за віком, дістаньте імена, зберіть у список». Не потрібно вручну писати цикли, створювати проміжні змінні й стежити, щоб нічого не забути.

Stream API працює як заводський конвеєр: ви визначаєте етапи обробки, а дані самі проходять через них. Виглядає охайно й компактно, а читається в рази простіше.

3. Переваги Stream API

  • Стислість і читабельність. Порівняйте два підходи — імперативний і декларативний — на одному й тому самому завданні. Одразу видно, який із них легше читати й підтримувати.
  • Проста композиція операцій. Операції легко «складати» в ланцюжок: filter, map, sorted, collect — і все це в одній зв’язній послідовності.
  • Менше помилок. Stream API звільняє від рутини: не потрібно вручну керувати проміжними колекціями й циклами — менший шанс забути крок або зробити помилку індексації.
  • Можливість паралелізму. Легко масштабувати обробку на кілька ядер, просто викликавши parallelStream() замість stream(). Подробиці — згодом.
  • Сучасний стиль програмування. Ідеї функціонального програмування (композиція функцій, відсутність явних циклів) підвищують виразність і цінуються на ринку.

Коротка історія Stream API

Stream API з’явився у Java 8 (2014) і став якісним стрибком для мови. До того часу будь-яка нетривіальна операція над колекціями вимагала багато допоміжного коду, тоді як інші платформи вже пропонували декларативні підходи (map, filter, reduce тощо).

Відтоді Stream API — стандарт де‑факто для обробки колекцій у Java. Якщо ви хочете писати сучасний Java‑код — без нього нікуди.

4. Сфери застосування Stream API

  • Фільтрація: вибрати лише потрібні елементи (наприклад, усі користувачі старші за 18 років).
  • Перетворення: витягнути поле або створити новий обʼєкт (наприклад, список імен користувачів).
  • Агрегація: порахувати суму, середнє, кількість, максимум/мінімум.
  • Сортування: упорядкувати елементи за потрібною ознакою.
  • Групування: розбити елементи за категоріями.
  • Збирання результату: зібрати в список, множину, мапу, рядок тощо.

Приклади завдань:

  • Отримати список e‑mail‑адрес усіх користувачів, чиї імена починаються на «А».
  • Порахувати середній вік користувачів.
  • Знайти першого користувача з певною e‑mail‑адресою.
  • Зібрати всі унікальні міста проживання в одну множину.

5. Корисні нюанси

Як працює потік

[User1] --\
[User2] ---|--> [ filter ] --> [ map ] --> [ collect ] --> List<String>
[User3] --/

1. filter — пропускає лише користувачів, які задовольняють умову.
2. map — перетворює користувача, наприклад, на e‑mail‑адресу.
3. collect — збирає e‑mail у потрібну колекцію.

Синтаксис: як створити потік

  • З колекції: list.stream() — потік елементів колекції.
  • З масиву: Arrays.stream(array)
  • З окремих значень: Stream.of("a", "b", "c")

6. Типові помилки під час переходу на Stream API

Помилка № 1: спроба змінити колекцію всередині потоку. Stream API не призначений для модифікації вихідної колекції (наприклад, не варто робити list.remove() у тілі forEach). Для видалень використовуйте, приміром, removeIf на колекції до або після обробки.

Помилка № 2: плутанина між колекцією та потоком. Потік — це не колекція! Потік «проходить» елементами один раз, після чого закривається. Якщо потрібно знову обробити дані — створіть новий stream().

Помилка № 3: надто складні ланцюжки. Не перетворюйте вираз на «монстра» з десятка операцій. Якщо ланцюжок стає довгим — розбийте його на кілька кроків із проміжними змінними для читабельності та налагодження.

Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ