Функциональный интерфейс
Лямбда реализует функциональный интерфейс — интерфейс ровно с одним абстрактным методом (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 и назовите ключевые стандартные интерфейсы с примитивными специализациями (против боксинга).