Лямбда-выражения
Определение
Лямбда — компактная реализация единственного абстрактного метода функционального интерфейса.
Comparator<String> byLength =
(left, right) -> Integer.compare(left.length(), right.length());Левая часть задаёт параметры, правая — одно выражение или блок:
Predicate<String> shortText = text -> text.length() < 20;
Function<String, String> normalize = text -> {
String trimmed = text.trim();
return trimmed.toLowerCase(Locale.ROOT);
};Лямбда не выполняется при объявлении. Она создаёт значение поведения; вызов происходит через SAM (test, apply, accept и т. п.).
Target typing
Лямбда получает тип из контекста:
Predicate<String> first = value -> value.isEmpty();
Function<String, Boolean> second = value -> value.isEmpty();Без целевого интерфейса выражение вроде value -> value.isEmpty() не имеет самостоятельного типа. Перегруженные методы с одинаковой формой SAM могут потребовать явного cast или, лучше, другого имени API.
Захват переменных
Лямбда может читать локальные переменные, если они final или effectively final:
int limit = 10;
Predicate<String> fits = text -> text.length() <= limit;После захвата limit нельзя переassignить. Ограничение предотвращает иллюзию совместной изменяемой локальной переменной, время жизни которой закончилось вместе с методом.
Поля объекта можно менять, но это уже общий mutable-state и требует обычных правил потокобезопасности.
this и область видимости
Лямбда не создаёт нового значения this: оно относится к окружающему объекту. В анонимном классе this относится к экземпляру самого анонимного класса.
Лямбда также не позволяет заново объявить локальное имя из внешней области:
int count = 1;
// Consumer<String> action = count -> {}; // конфликт имениСсылки на методы
Четыре формы:
String::valueOf // static-метод
text::startsWith // метод конкретного объекта
String::length // instance-метод произвольного объекта типа
ArrayList<String>::new // конструкторСсылка должна соответствовать функциональному дескриптору:
Function<String, Integer> length = String::length;
Supplier<List<String>> factory = ArrayList::new;Ссылка на метод хороша, когда имя проясняет действие. Если для понимания нужно угадывать перегрузку, явная лямбда читабельнее.
Checked-исключения
Тело может выбрасывать checked-исключение только если SAM это разрешает:
@FunctionalInterface
interface Parser<T> {
T parse(Path path) throws IOException;
}
Parser<String> parser = Files::readString;Стандартный Function<Path,String> такого throws не имеет.
Лямбда и identity
Не следует полагаться на идентичность объекта лямбды, его конкретный класс или сериализуемость. JVM свободна переиспользовать экземпляры и реализовывать их различными способами. Контракт — только поведение функционального интерфейса.
Ловушки
- Считать лямбду самостоятельным типом.
- Менять захваченное состояние без синхронизации.
- Использовать массив/обёртку как обход effectively final и прятать mutable-счётчик.
- Заворачивать checked-ошибку в безымянный
RuntimeException. - Выбирать ссылку на перегруженный метод, если лямбда яснее.
- Выполнять I/O или бизнес-эффекты внутри
mapбез явного контракта.
Вопросы на собеседовании
- Во что типизируется лямбда?
- Что означает effectively final?
- Чем
thisв лямбде отличается отthisв анонимном классе? - Какие четыре формы ссылок на методы существуют?
- Может ли лямбда выбросить checked-исключение?
- Можно ли полагаться на класс или идентичность объекта лямбды?
Связанные заметки
Функциональные интерфейсы · ФИ стандартной библиотеки Java · Функциональное программирование · Stream API · Вложенные классы