Статьи

Введение в Java лямбды

Основная тема Java 8 — лямбды. Я заметил, что для многих программистов на Java лямбды — довольно жесткий материал. Итак, давайте попробуем получить общее представление о них.

Прежде всего, что такое лямбда? Лямбда — это анонимная функция, которая, в отличие от обычной функции, не связана с идентификатором (то есть не имеет имени). Эти функции могут быть переданы в качестве аргументов другим функциям (известным как функции высшего порядка).

Предположим, что наше приложение должно записывать в кучу файлов из разных мест системы. Мы не хотим обрабатывать проверенные исключения каждый раз [см. Исключения: проверено и не отмечено для получения дополнительной информации о проверенных исключениях]. Поэтому мы решили написать низкоуровневую функцию writeToFile которая открывает FileWriter и передает его функции, которая затем может безопасно записать в файл.

Используя эту низкоуровневую функцию, мы напишем следующий код.

1
2
3
4
5
6
7
writeToFile("todo.txt", new FileWriteFunction() {
    @Override
    public void apply(Writer file) throws IOException {
        file.write("learn about lambdas\n");
        file.write("learn stream API\n");
    }
})

Объект, который мы передаем в writeToFile является анонимной реализацией FileWriteFunction [анонимно, потому что мы не writeToFile его как класс]. Он имеет единственную функцию, позволяющую эффективно передавать анонимную функцию. В мире Java их иногда называют обратными вызовами . Возможно, вы использовали это, по крайней мере, несколько раз раньше, возможно, не обращая внимания.

Этот анонимный объект по сути является лямбда-выражением. Но, очевидно, это не похоже на передачу функции. Синтаксис очень громоздкий. И это именно то, что меняется в Java 8.

Благодаря синтаксической поддержке лямбд в Java 8 код читается так, как будто мы передаем функцию. Используя лямбду Java 8, мы переписываем приведенный выше код следующим образом.

1
2
3
4
writeToFile("todo.txt", file -> {
    file.write("learn about lambdas\n");
    file.write("learn stream API\n");
})

Так-то лучше. Он подчеркивает код, который имеет значение и скрывает большую часть громоздких частей.

Часто лямбды используются взаимозаменяемо с замыканиями (то есть лексическими функциями). Хотя они обе являются анонимными функциями, определение замыкания состоит в том, что это функция, содержащая связанные переменные. Т.е. замыкание включает в себя таблицу ссылок, которая содержит ссылки на локальные переменные.

Например, если мы принимаем data параметров, которые мы хотим записать в файл, мы используем замыкание.

1
2
3
void save(String data) {
    writeToFile("file.db", file -> file.write(data) );
}

В то время как анонимные внутренние классы ограничивают доступ к конечным переменным, замыкания предоставляют доступ к любой переменной. Тем не менее, переменная является окончательной для закрытия, поэтому ее нельзя переназначить.

Как насчет составления лямбд? Предоставляет ли Java 8 только ложку синтаксических сахаров для анонимных внутренних классов только одним методом?

Не на самом деле нет. Это правда, что он допускает лямбда-синтаксис для любого анонимного внутреннего метода с одним методом. Но лямбды не скомпилированы во внутренние классы. Вместо этого компилятор выводит lambda$ методы в определяющем классе и использует invokedynamic для отправки вызова.

Итак, теперь вы знаете, как использовать лямбду в Java 8. Хотя лямбды сами по себе довольно полезны, они особенно полезны при применении их к коллекциям.

Новый Stream API предоставляет альтернативу итераторам, предлагая более функциональный API для коллекций: java.util.stream.Stream . Наиболее примечательные функции Stream : collect , filter , map и reduce .

Чтобы начать с простого примера, вот как суммировать все числа в списке.

1
2
asList(1,2,3,4,5).stream()
    .reduce(0, (acc, value) -> acc + value) // => 15

Это уменьшает последовательность, добавляя каждое значение к аккумулятору, начиная с нуля. Для сравнения классически вы бы написали цикл.

1
2
3
4
int acc = 0;
for (int n : asList(1,2,3,4,5))
    acc += n;
acc // => 15

Переходим к суммированию только нечетных чисел. Сначала мы filter нечетные числа, затем reduce .

1
2
3
asList(1,2,3,4,5).stream()
    .filter(Predicates::odd)
    .reduce(0, (acc, n) -> acc + n) // => 9

Аргумент для filter — это ссылка на функцию к odd статической функции в классе Predicates я использовал. Это логическая функция, которая, как следует из названия, проверяет, является ли число нечетным.

Все идет нормально. Теперь предположим, что мы хотим преобразовать список размеров сантиметров в их эквивалентный размер в дюймах. Мы используем map для этого.

1
2
3
List<Inch> inches = centimeters.stream()
    .map(Centimeter::toInches)
    .collect(Collectors.toList())

toInches отображаются в дюймы с помощью функции toInches для каждого элемента в коллекции centimeters .

По своей природе Stream непрерывен. Он используется для описания операций, применяемых к коллекции. Но для получения результатов данные должны быть собраны. Для этого предназначена функция collect . Это уменьшает элементы потока в изменяемый контейнер [например, список].

Использование Stream API и lambdas может значительно упростить код, который вы используете для работы с коллекциями, и сделать код намного более выразительным. Предпочтение использования неразрушающих операций (например, map , filter ) по сравнению с разрушительными операциями (например, forEach ) делает код более легким для рассуждения.

Это оно! Это основы, которые вы должны знать о лямбдах (и замыканиях) в Java 8. Конечно, о лямбдах можно написать гораздо больше, но это кое-что для другого поста.

Ссылка: Знакомство с лямбдами Java от нашего партнера по JCG Барта Баккера в блоге Software Craft .