Увидим ли логи при вызове map с функцией логирования в Java Stream API
Что проверяют
Понимаете ли вы, что промежуточные операции стрима ничего не делают до терминальной, и что элементы идут по конвейеру по одному, а не слоями. Вопрос любят задавать именно через логирование, потому что результат видно глазами.
Как отвечать
Логов не будет, пока не вызвана терминальная операция. map только собирает конвейер:
Stream<String> s = names.stream()
.map(n -> { System.out.println(n); return n.toUpperCase(); });
// тут пусто
List<String> r = s.collect(Collectors.toList()); // вот теперь печатаетДальше стоит сказать то, что отличает подготовленного кандидата от заучившего слово «ленивость»:
- порядок обработки поэлементный: первый элемент проходит всю цепочку, потом второй, поэтому логи из соседних
mapчередуются, а не идут блоками; - при коротком замыкании печатается не всё: с
findFirstилиlimit(1)лог будет только для первого элемента; filterпередmapотсекает часть элементов, и до логирования они не доходят;- стрим одноразовый, повторный вызов терминальной операции даёт
IllegalStateException, а не второй проход логов.
Для логирования по смыслу подходит peek, но и он не гарантия: если терминальная операция способна посчитать результат, не трогая элементы, движок вправе пропустить всю цепочку.
Где спотыкаются
Отвечают «логи будут, ведь map вызван», потому что мысленно приравнивают стрим к циклу. Помогает простая проверка: результат промежуточной операции это снова стрим, а описание работы не есть её выполнение.
Вторая ошибка: ждут, что напечатается вся коллекция, и не учитывают короткое замыкание. На anyMatch по десяти тысячам элементов в логе может оказаться одна строка, и это не баг, а нормальная работа конвейера.
Третья связана с count. Когда размер известен заранее и цепочка не меняет количество элементов, подсчёт делается без обхода, а побочные эффекты внутри map или peek не выполняются вовсе. Терминальную операцию тут называют верно, а поведения не ждут.