Введение в Stream API

Stream API в Java — это способ декларативно описывать обработку коллекций и других источников данных. Вместо ручного цикла с переменными вы описываете цепочку операций: «отфильтровать», «преобразовать», «собрать в список». Поток (Stream) — не структура данных, а конвейер: данные «протекают» через цепочку операций один раз.

В контексте AlashEd Stream API удобен, когда нужно обработать массив измерений с датчиков: отфильтровать выбросы, перевести единицы, посчитать среднее.

Источники и операции

Источники:
    collection.stream()
    Arrays.stream(array)
    Stream.of(a, b, c)
    IntStream.range(0, 10)
    Files.lines(path)

Промежуточные (lazy, возвращают Stream):
    filter, map, mapToInt, flatMap, distinct, sorted, limit, skip, peek

Терминальные (запускают конвейер):
    forEach, count, toArray, collect, reduce, min, max, anyMatch, findFirst

Жизненный цикл потока

  1. Получили поток из источника.

  2. Накопили цепочку промежуточных операций — ничего ещё не выполняется.

  3. Вызвали терминальную операцию — Java запускает конвейер один раз.

  4. После терминальной операции поток считается использованным, повторно его использовать нельзя.

Примеры

Пример 1. Простейший pipeline

import java.util.Arrays;
import java.util.List;

public class StreamHello {
    public static void main(String[] args) {
        List<Integer> distances = Arrays.asList(12, 8, 25, 3, 40);
        long n = distances.stream()
                          .filter(d -> d > 10)
                          .count();
        System.out.println("Измерений > 10 см: " + n);
    }
}

Output:

Измерений > 10 см: 3

Пример 2. Источники потока

import java.util.Arrays;
import java.util.stream.IntStream;
import java.util.stream.Stream;

public class StreamSources {
    public static void main(String[] args) {
        Stream.of("a", "b", "c").forEach(System.out::println);

        int[] arr = {1, 2, 3};
        int sum = Arrays.stream(arr).sum();
        System.out.println("Сумма: " + sum);

        int squares = IntStream.range(1, 5).map(x -> x * x).sum();
        System.out.println("Сумма квадратов 1..4: " + squares);
    }
}

Output:

a
b
c
Сумма: 6
Сумма квадратов 1..4: 30

Пример 3. Ленивость

import java.util.stream.Stream;

public class StreamLazy {
    public static void main(String[] args) {
        Stream.of("ИК", "УЗ", "Линия", "Сервопривод")
              .peek(s -> System.out.println("вижу: " + s))
              .filter(s -> s.length() > 2)
              .findFirst()
              .ifPresent(s -> System.out.println("найдено: " + s));
    }
}

Output:

вижу: ИК
вижу: УЗ
вижу: Линия
найдено: Линия

Поток дошёл только до первого подходящего элемента — остальное не обрабатывалось.

Пример 4. Терминальные операции возвращают значение

import java.util.Arrays;
import java.util.List;
import java.util.OptionalInt;

public class StreamTerminal {
    public static void main(String[] args) {
        List<Integer> values = Arrays.asList(7, 14, 3, 21, 9);
        OptionalInt max = values.stream().mapToInt(Integer::intValue).max();
        System.out.println("Максимум: " + max.getAsInt());

        boolean hasNegative = values.stream().anyMatch(v -> v < 0);
        System.out.println("Есть отрицательные: " + hasNegative);
    }
}

Output:

Максимум: 21
Есть отрицательные: false

Пример 5. Поток нельзя переиспользовать

import java.util.stream.Stream;

public class StreamReuse {
    public static void main(String[] args) {
        Stream<String> s = Stream.of("a", "b", "c");
        s.forEach(System.out::println);
        try {
            s.forEach(System.out::println);  // IllegalStateException
        } catch (IllegalStateException e) {
            System.out.println("Ошибка: поток уже использован");
        }
    }
}

Output:

a
b
c
Ошибка: поток уже использован

Подводные камни

Предупреждение

  • Поток можно использовать только один раз. Если нужны два прохода — получайте поток заново из источника.

  • Промежуточные операции ленивые — без терминальной операции конвейер не выполнится.

  • Не изменяйте источник во время работы потока — поведение неопределено.

  • parallelStream() ускоряет работу не всегда: для маленьких коллекций накладные расходы съедают выигрыш.

  • IntStream / LongStream / DoubleStream избавляют от боксинга и обычно быстрее, чем Stream<Integer>.

См. также

Примечание

Материал подготовлен по мотивам документации пакета java.util.stream и распространяется в рамках Oracle Free Documentation License. Текст написан своими словами, примеры кода — оригинальные.