Основная тема 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. Конечно, о лямбдах можно написать гораздо больше, но это кое-что для другого поста.