Урок по треку Java 8 мин

Лямбды и Stream API

Функциональные интерфейсы, ссылки на методы, ленивые стримы, коллекторы, Optional.

Стримы и лямбды появились в Java 8 и с тех пор есть в каждом проекте. На собесе просят написать группировку на стримах, объяснить ленивость и разницу map и flatMap, а на senior спрашивают про parallel и цену абстракции.

Функциональные интерфейсы и лямбды

Функциональный интерфейс имеет ровно один абстрактный метод (аннотация @FunctionalInterface проверяет это). Лямбда это компактная реализация такого интерфейса.

ИнтерфейсМетодСмысл
Supplier<T>T get()дать значение
Consumer<T>void accept(T)использовать значение
Function<T, R>R apply(T)преобразовать
Predicate<T>boolean test(T)проверить
UnaryOperator<T>, BinaryOperator<T>T apply(T), T apply(T, T)операции над одним типом

Для примитивов есть IntFunction, ToIntFunction, IntPredicate и другие: без упаковки.

Лямбда может читать локальные переменные, только если они effectively final (не меняются после инициализации). Причина: лямбда захватывает копию значения, и менять оригинал было бы обманом. Поля класса менять можно.

Ссылки на методы: String::length (метод экземпляра на аргументе), System.out::println (на конкретном объекте), Integer::parseInt (статический), ArrayList::new (конструктор).

Как устроен стрим

Источник → промежуточные операции → терминальная операция.

Map<String, Long> byCity = users.stream()
    .filter(u -> u.age() >= 18)
    .collect(Collectors.groupingBy(User::city, Collectors.counting()));
  • Промежуточные (filter, map, flatMap, sorted, distinct, peek, limit) ленивые: только описывают конвейер.
  • Терминальные (collect, forEach, reduce, count, findFirst, anyMatch, toList) запускают обработку.
  • Элементы проходят конвейер по одному, вертикально: первый элемент проходит filter и map, потом второй. Исключение: sorted и distinct с состоянием, sorted сначала собирает всё.
  • Короткое замыкание: findFirst, anyMatch, limit останавливают обработку, как только ответ известен.
  • Стрим одноразовый: второй терминальный вызов бросает IllegalStateException.

Без терминальной операции ничего не выполнится, даже peek с печатью.

map и flatMap

map превращает каждый элемент в один новый, flatMap в стрим элементов и склеивает их:

List<List<Integer>> nested = List.of(List.of(1, 2), List.of(3));
nested.stream().flatMap(List::stream).toList(); // [1, 2, 3]

Коллекторы

  • toList() (метод стрима, Java 16) возвращает неизменяемый список, Collectors.toList() на практике отдаёт изменяемый ArrayList, но спецификация тип и изменяемость не обещает.
  • toMap(k, v) падает с IllegalStateException на дубле ключа, если не дать функцию слияния третьим аргументом.
  • groupingBy(classifier, downstream): counting(), summingInt, mapping, toSet().
  • partitioningBy(predicate): две группы, true и false.
  • joining(", ") для строк.

Optional

Контейнер «значение или ничего» для возвращаемых значений. map, filter, orElse, orElseGet, orElseThrow. Важно: orElse(x) вычисляет x всегда, orElseGet(() -> x) только если пусто. Optional не используют для полей, параметров и коллекций, и никогда не возвращают null вместо Optional.

Параллельные стримы

parallelStream() делит работу на общем ForkJoinPool.commonPool(). Помогает на больших объёмах CPU-работы с хорошо делимым источником (ArrayList, массив). Вредит на маленьких данных, блокирующих операциях (заберут общий пул) и на LinkedList. Лямбды не должны менять общее состояние: forEach(list::add) в параллели теряет элементы.

Частые ошибки

  • Побочные эффекты в map и peek.
  • toMap без функции слияния на данных с дублями.
  • orElse(expensiveCall()), который вызывается всегда.
  • Повторное использование стрима.

Что спросят на собесе

  • Что такое функциональный интерфейс и какие стандартные знаете?
  • Почему переменная в лямбде должна быть effectively final?
  • Промежуточные и терминальные операции, что такое ленивость?
  • Разница map и flatMap, orElse и orElseGet.
  • Когда parallelStream ускоряет, а когда вредит?

Закрепите за 5 минут

После урока в тренажёре 3-5 проверочных вопросов, дальше модуль уходит в повторения.