Encodera

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.

Оглавление

Лямбда-выражения

Иногда методу нужно передать не данные, а действие: как сравнивать элементы, что удалять, что делать с каждым. Лямбда-выражение — это короткая запись такого действия, функция без имени, которую можно сохранить в переменную или передать в метод.

Первая лямбда

Лямбда состоит из параметров, стрелки -> и тела. Здесь лямбда получает число и возвращает его квадрат, а метод apply() её вызывает:

void main() {
    Function<Integer, Integer> square = x -> x * x;
    IO.println(square.apply(5));
}

Результат:

25

Function<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), о которых пойдёт речь в следующей главе.

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.