Получить доступ

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

SAM-интерфейсы, лямбда против анонимного класса, замыкания и effectively final, ссылки на методы и стандартные интерфейсы.

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

Лямбда реализует функциональный интерфейс — интерфейс ровно с одним абстрактным методом (SAM, single abstract method). default- и static-методы не считаются, поэтому интерфейс с ними всё ещё функциональный.

Аннотация @FunctionalInterface необязательна, но полезна: компилятор проверит, что абстрактный метод действительно один, и не даст случайно сломать контракт при добавлении второго.

Лямбда против анонимного класса

Лямбда — это не «короткий анонимный класс». Анонимный класс компилируется в отдельный .class и создаёт новый объект; лямбда транслируется через invokedynamic и LambdaMetafactory, и для лямбды без захвата состояния экземпляр может переиспользоваться (не плодит объекты).

Ключевое различие в семантике this: внутри лямбды this — это ОКРУЖАЮЩИЙ объект (лямбда не вводит свой scope), а внутри анонимного класса this — сам анонимный объект. Это влияет и на затенение имён.

Замыкания и effectively final

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

Почему так: локальные переменные живут на стеке и исчезают с кадром метода, поэтому в лямбду захватывается их ЗНАЧЕНИЕ (копия); разрешить мутацию значило бы менять копию незаметно для оригинала и наоборот. Если нужен изменяемый «счётчик» из лямбды — используют поле объекта, массив-холдер или Atomic-классы, а не локальную переменную.

int[] count = {0};
items.forEach(x -> count[0]++); // обход запрета: мутируем содержимое, а не саму ссылку

Ссылки на методы

Method reference — ещё более краткая форма лямбды, когда та просто вызывает существующий метод. Четыре вида: на статический метод (Integer::parseInt), на метод конкретного объекта (logger::info), на метод произвольного объекта-типа (String::length — первый аргумент становится получателем) и на конструктор (ArrayList::new).

Они читаются лучше лямбды и часто подсказываются IDE; семантически это та же реализация функционального интерфейса.

Стандартные функциональные интерфейсы

В java.util.function есть готовый набор: Function<T,R> (преобразование), Predicate<T> (условие), Consumer<T> (приём без результата), Supplier<T> (поставка), плюс BiFunction, UnaryOperator/BinaryOperator (когда типы совпадают). Их и принимают методы Stream/Optional/CompletableFuture.

Чтобы избежать боксинга, есть примитивные специализации: IntPredicate, ToIntFunction, IntUnaryOperator и т.д. Интерфейсы комбинируются: Function.andThen/compose, Predicate.and/or/negate, Consumer.andThen.

Как отвечать на собеседовании

Дайте определение функционального интерфейса (один абстрактный метод) и @FunctionalInterface, объясните отличие лямбды от анонимного класса (invokedynamic, без нового класса; this = окружающий объект), разверните effectively final через «захват значения локалки со стека» и обход через поле/holder, перечислите 4 вида method reference и назовите ключевые стандартные интерфейсы с примитивными специализациями (против боксинга).

Вопросы по теме

Читайте также

Закрытая бета

Знать тему — половина дела

На живом собеседовании sotto слышит вопрос интервьюера и показывает короткий ответ и план. Оставьте контакт — пришлём доступ и промокод.

  • Промокод на 2 часа — сверх пробных 30 минут
  • Программа для Windows — скачать сразу, войти — через Яндекс ID или VK ID
  • Прямая связь с разработчиком: что улучшить — сделаем