Введение в 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
Жизненный цикл потока
Получили поток из источника.
Накопили цепочку промежуточных операций — ничего ещё не выполняется.
Вызвали терминальную операцию — Java запускает конвейер один раз.
После терминальной операции поток считается использованным, повторно его использовать нельзя.
Примеры
Пример 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. Текст написан своими словами, примеры кода — оригинальные.