Лабораторная работа № 7 — «Лямбда-выражения, функциональные интерфейсы и Stream API»
Эта работа превращает уже существующий список транзакций в аналитическую систему.
Сначала студент без Stream API имеет методы:
List<Transaction> findExpenses(...) BigDecimal totalByCategory(...) Map<Category, BigDecimal> totals(...)
и затем переписывает их декларативно.
Вводятся стандартные функциональные интерфейсы:
Predicate<Transaction> Function<Transaction, BigDecimal> Consumer<Transaction> Supplier<List<Transaction>>
Лямбды в Java являются средством предоставления реализации функционального интерфейса; официальные материалы Java рассматривают их именно в этой связи.
Обязательные задачи:
найти все расходы за период; найти операции больше заданной суммы; отсортировать операции по убыванию суммы; получить список описаний; посчитать сумму расходов; сгруппировать расходы по категории; найти категорию с максимальными расходами; получить пять крупнейших расходов.
Например:
var result = transactions.stream() .filter(...) .sorted(...) .limit(5) .toList();
Студент должен в каждом pipeline явно пометить:
источник → промежуточные операции → терминальную операцию
Java Stream API разделяет операции на intermediate и terminal; вычисление pipeline инициируется терминальной операцией, а многие промежуточные операции выполняются лениво.
Эксперимент на ленивость:
Stream<Transaction> stream = transactions.stream() .filter(t -> { System.out.println("filter: " + t.getId()); return ...; }); // ничего больше не делать
Затем добавить .count() или .toList() и сравнить вывод.
Эксперимент «плохой Stream».
Студенту предлагается:
BigDecimal[] total = {BigDecimal.ZERO}; transactions.stream() .forEach(t -> total[0] = total[0].add(t.getAmount()));
и требуется переписать без изменяемого внешнего состояния.
parallelStream() — только эксперимент, не “ускоритель”. Студент запускает одну и ту же задачу через stream() и parallelStream(), наблюдает порядок выполнения и объясняет, почему нельзя исходить из правила «parallel всегда быстрее». Официальная документация прямо предупреждает, что функции в stream pipeline должны быть non-interfering/stateless, а порядок forEach в параллельной обработке не гарантируется.
В отчёте обязательно сравниваются императивный и stream-варианты по читаемости, а не только по числу строк.
Вопросы для углубления: Что такое лямбда-выражение и функциональный
интерфейс?Какие задачи решают Predicate,
Function, Consumer и т. п. ?Как устроен конвейер
Stream: промежуточные и терминальные
операции?Что означает «ленивость» вычислений в
Stream API?В чем особенности и риски
parallelStream()?
