Лямбда-выражения
Иногда методу нужно передать не данные, а действие: как сравнивать элементы, что удалять, что делать с каждым. Лямбда-выражение — это короткая запись такого действия, функция без имени, которую можно сохранить в переменную или передать в метод.
Первая лямбда
Лямбда состоит из параметров, стрелки -> и тела. Здесь лямбда получает число и возвращает его квадрат, а метод apply() её вызывает:
void main() {
Function<Integer, Integer> square = x -> x * x;
IO.println(square.apply(5));
}Результат:
25Function<Integer, Integer> — тип переменной: «функция, которая принимает Integer и возвращает Integer». Такие типы называются функциональными интерфейсами, о них следующий урок.
Формы записи
Если параметров нет или их несколько, скобки обязательны. Если в теле несколько строк, его берут в фигурные скобки и пишут return:
void main() {
Supplier<String> hello = () -> "Привет!";
BinaryOperator<Integer> add = (a, b) -> a + b;
Function<String, String> shout = text -> {
String upper = text.toUpperCase();
return upper + "!";
};
IO.println(hello.get());
IO.println(add.apply(2, 3));
IO.println(shout.apply("тише"));
}Результат:
Привет!
5
ТИШЕ!Типы параметров обычно не пишут — Java выводит их из контекста.
Лямбда как аргумент
Чаще всего лямбду сразу передают в метод. Вы уже встречали это в главе о коллекциях: removeIf() принимает условие, sort() — способ сравнения, forEach() — действие:
void main() {
List<String> words = new ArrayList<>(List.of("дом", "кот", "автобус", "ёж"));
words.removeIf(w -> w.length() < 3);
words.sort((a, b) -> b.length() - a.length());
words.forEach(w -> IO.println(w));
}Результат:
автобус
дом
котСвой метод, принимающий лямбду
Чтобы метод принимал лямбду, у параметра должен быть тип функционального интерфейса. Тогда один и тот же метод делает разную работу в зависимости от переданного действия:
List<Integer> transform(List<Integer> numbers, Function<Integer, Integer> action) {
List<Integer> result = new ArrayList<>();
for (int n : numbers) {
result.add(action.apply(n));
}
return result;
}
void main() {
List<Integer> prices = List.of(10, 20, 30);
IO.println(transform(prices, p -> p * 2));
IO.println(transform(prices, p -> p - 5));
}Результат:
[20, 40, 60]
[5, 15, 25]Захват переменных
Лямбда может пользоваться переменными из окружающего метода. Это называется захватом. Такая переменная должна быть фактически финальной — после присваивания она больше не меняется:
void main() {
double rate = 1.2;
Function<Double, Double> withTax = price -> price * rate;
IO.println(withTax.apply(50.0));
}Результат:
60.0Частая ошибка
Изменять захваченную локальную переменную внутри лямбды нельзя — например, считать сумму через forEach():
void main() {
int total = 0;
List.of(1, 2, 3).forEach(n -> total += n);
}Результат:
Main.java:3: error: local variables referenced from a lambda expression must be final or effectively final
List.of(1, 2, 3).forEach(n -> total += n);
^Для подсчёта используйте обычный цикл for-each или потоки (stream), о которых пойдёт речь в следующей главе.