Что будет, если вызвать две терминальные операции подряд в Java Stream?
Что проверяют
Понимаете ли вы, что поток это одноразовый конвейер, а не коллекция. За этим обычно сразу идёт разговор про ленивость промежуточных операций и про то, где именно исполняется код.
Как отвечать
Вторая терминальная операция на том же объекте потока бросит IllegalStateException с текстом про то, что поток уже использован или закрыт. Проверка стоит на связывании стадий, поэтому тот же эффект даст и промежуточная операция, дописанная после терминальной.
Stream<String> s = names.stream().filter(n -> n.startsWith("A"));
long count = s.count();
s.forEach(System.out::println); // IllegalStateExceptionСразу уточните, где границы: проблема ровно в повторном использовании одной переменной. Два вызова names.stream() подряд создают два независимых потока, и такой код полностью корректен, просто источник читается дважды.
Если результат нужен дважды, вариантов три:
- собрать один раз в коллекцию и работать с ней;
- хранить
Supplier<Stream<T>>и зватьget()перед каждым проходом; - посчитать всё за один проход. У числовых потоков есть
summaryStatisticsс количеством, суммой, минимумом, максимумом и средним, два коллектора объединяются черезCollectors.teeing, а два прохода с разными фильтрами часто заменяются наpartitioningByилиgroupingBy.
Где спотыкаются
Думают, что упадёт первый вызов или что второй просто вернёт пустой результат. Первая операция отрабатывает штатно и возвращает корректные данные, исключение бросает вторая, и происходит это во время выполнения, а не на компиляции.
Второе: переиспользуют поток, полученный параметром метода. Метод с сигнатурой на Stream обязан считать аргумент одноразовым, и если нужны два прохода, в сигнатуре должна стоять коллекция или поставщик потока.
Третье: путают одноразовость с ленивостью. Без терминальной операции конвейер не выполняется вовсе, поэтому цепочка с map и без терминала не вызовет функцию ни разу, и побочный эффект, положенный внутрь, не случится.