Как начать работать с Лямбда-выражениями в Java
Привет, Хабр! Представляю вашему вниманию перевод статьи «How to start working with Lambda Expressions in Java» автора Luis Santiago.
До того как Лямбда-выражения были добавлены в JDK 8, я использовал их в таких языках как C# и С++. Когда они были добавлены в Java я стал изучать их подробнее.
С добавлением Лямбда-выражений добавились элементы синтаксиса, которые увеличивают «выразительную силу» Java. В этой статье я хочу сосредоточиться на основополагающих концепциях, с которыми вам необходимо познакомиться, чтобы начать использовать Лямбда-выражения.
Краткое введение
Лямбда-выражения используют преимущества параллельных процессов в многоядерных средах, что видно при поддержке операций с конвейерами данных в Stream API.
Это анонимные методы (методы без имени), используемые для реализации метода, определенного функциональным интерфейсом. Важно знать, что такое функциональный интерфейс, прежде чем вы начнете использовать Лямбда-выражения.
Функциональный интерфейс
Функциональный интерфейс — это интерфейс, содержащий один и только один абстрактный метод.
Если вы посмотрите на определение стандартного интерфейса Runnable, то вы заметите как он попадает в определение функционального интерфейса, поскольку он определяет только один метод: run().
Оператор Стрелка
В левой части задаются параметры, необходимые для выражения. Эта часть может быть пустой если не требуется никаких параметров.
Правая сторона — это тело выражения, которое определяет его действия.
Теперь, используя функциональные выражения и оператор стрелки можно составить просто лямбда-выражение:
Переменные morningGreeting и eveningGreeting в строках 6 и 7 соответственно в примере выше создают ссылку на интерфейс MyGreeting и определяют 2 выражения приветствия.
При написании лямбда-выражения можно явно указать тип параметра, как это делается в примере ниже:
Блок Лямбда-выражений
До сих пор я рассматривал одиночные лямбда-выражения. Существует еще один тип выражения, когда справа от оператора стрелки находится не одно простое выражение и так называемый блок лямбда:
Функциональные интерфейсы generic
Лямбда-выражения не могут быть generic, но функциональный интерфейс, связанный с выражением, может. Можно написать один общий интерфейс и возвращать различные типы данных, например:
Использование Лямбда-выражений в качестве аргументов
Одно распространенное использование лямбда — передача их в качестве аргументов.
Вы можете передавать исполняемый код аргументам методов в качестве параметров. Для этого просто убедитесь, что тип функционального интерфейса совместим с требуемым параметром.
Эти концепции дадут вам хорошую основу для начала работы с лямбда-выражениями. Взгляните на свой код и посмотрите, где вы можете их использовать.
Лямбда-выражения в Java
Поддержка лямбда-выражений, реализованная в Java 8, стала одним из наиболее значимых нововведений за последнее время. Будучи упрощённой записью анонимных классов, лямбды позволяют писать более лаконичный код при работе со Stream или Optional. Лямбда-выражения часто используются как совместно со многими API стандартной библиотеки Java, так и со сторонними API, среди которых JavaFX, реактивные стримы и т.д.
Лямбды и функциональные интерфейсы
Лямбда-выражение или просто лямбда в Java — упрощённая запись анонимного класса, реализующего функциональный интерфейс.
Функциональный интерфейс в Java — интерфейс, в котором объявлен только один абстрактный метод. Однако, методов по умолчанию (default) такой интерфейс может содержать сколько угодно, что можно видеть на примере java.util.function.Function. Функциональный интерфейс может быть отмечен аннотацией @FunctionalInterface, но это не обязательное условие, так как JVM считает функциональным любой интерфейс с одним абстрактным методом.
Пример простого функционального интерфейса:
Структура лямбда-выражения
Сигнатура лямбда-выражения соответствует сигнатуре абстрактного метода реализуемого функционального интерфейса. Можно даже сказать, что лямбда-выражение является реализацией абстрактного метода этого функционального интерфейса. Главное отличие сигнатуры лямбда-выражения от сигнатуры метода в том, что она состоит только из двух частей: списка аргументов и тела, разделённых при помощи «->». Возвращаемый тип и возможные выбрасываемые исключения JVM берёт из интерфейса.
Типы аргументов лямбда-выражения опциональны, так как они декларируются интерфейсом, но при использовании обобщений (дженериков) с extends/super может возникнуть необходимость в указании конкретных типов аргументов. При этом стоит отметить, что типы либо указываются для всех аргументов, либо не указываются вообще. Это же касается и использования var, введённой в Java 11. Всё это можно свести к такому правилу: все аргументы объявляются либо с типами, либо с var, либо без них.
Если у лямбда-выражения всего один аргумент, и для него не требуется объявление типа или var, то круглые скобки можно опустить. В остальных случаях, в том числе если лямбда не принимает никаких аргументов, скобки нельзя опустить.
Аналогичная ситуация и с телом лямбда-выражений: если оно состоит только из одной строки, то фигурные скобки, точку с запятой (;) и директиву return можно тоже опустить.
В качестве тела лямбда-выражения может использоваться ссылка на метод.
Создание лямбда-выражений
Допустим, нам нужна реализация CarFilter, описанного выше, которая проверяла бы, что автомобиль выпущен не раньше 2010 года. Если мы будем использовать анонимный класс, то создание объекта CarFilter будет выглядеть примерно следующим образом:
Но мы можем описать объект CarFilter при помощи лямбда-выражения:
Однако, эту запись можно сделать ещё меньше:
Согласитесь, что такая запись зачительно меньше и лаконичнее, чем использование анонимного класса.
Применение лямбда-выражений
Допустим у нас есть задача написать метод, выводящий из полученного списка автомобили, у которых тип кузова (body) — STATION_WAGON и мощность (power) — больше 200 л.с.
Скорее всего, мы напишем что-то вроде:
В целом, если нам требуется всего один подобный метод, то этот код можно оставить без изменений и даже не задумываться об использовании лямбда-выражений. Но, допустим, у нас появляется задача реализовать ещё один метод, который бы выводил все автомобили, у которых кузов не PICKUP_TRUCK, или метод, который бы сохранял в БД все автомобили с мощностью двигателя более 150 л.с.
В этом случае логично было бы использовать сразу два функциональных интерфейса: java.util.function.Predicate — для фильтрации и java.util.function.Consumer — для действия, применяемого к подходящим объектам.
java.util.function.Predicate декларирует абстрактный метод test, который принимает объект и возвращает значение типа boolean в зависимости от соответствия переданного объекта требуемым критериям.
java.util.function.Consumer декларирует абстрактный метод accept, который принимает объект и выполняет над ним требуемые действия.
Метод printCars превратится во что-то похожее на следующий метод:
И первоначальную задачу вывести из полученного списка автомобили, у которых тип кузова (body) — STATION_WAGON и мощность (power) — больше 200 л.с. мы решили бы следующим вызовом метода processCars с использованием лямбда-выражений:
Или при помощи анонимных классов:
Вариант вызова метода processCars с использованием лямбда-выражений значительно компактнее.
Лямбды, анонимные классы и обычные классы
Как уже было написано, лямбда-выражения могут заменить анонимные классы, которые реализуют функциональные интерфейсы, но в остальных случаях анонимные классы не теряют актуальности.
Если одно и то же лямбда-выражение (или анонимный класс) используется в нескольких случаях, то появляется смысл сделать его членом класса или объекта, или и вовсе написать полноценный класс, реализующий необходимый интерфейс.
Но в большинстве случаев, там где можно применять лямбда-выражения, например в Stream, Optional или CompletableFuture, логичнее применять именно лямбды.
Lambda-выражения в Java
Привет, Хабр! Представляю вашему вниманию перевод статьи «Java Lambda Expressions» автора www.programiz.com.
Введение
В этой статье, с помощью примеров, мы изучим lambda-выражения в Java, их использование с функциональными интерфейсами, параметризированными функциональными интерфейсами и Stream API.
Лямбда выражения были добавлены в Java 8. Их основная цель – повысить читабельность и уменьшить количество кода.
Но, прежде чем перейти к лямбдам, нам необходимо понимать функциональные интерфейсы.
Что же такое функциональный интерфейс?
Если интерфейс в Java содержит один и только один абстрактный метод, то он называется функциональным. Этот единственный метод определяет назначение интерфейса.
Например, интерфейс Runnable из пакета java.lang является функциональным, потому, что он содержит только один метод run().
Пример 1: объявление функционального интерфейса в java
В приведенном выше примере, интерфейс MyInterface имеет только один абстрактный метод getValue(). Значит, этот интерфейс — функциональный.
Здесь мы использовали аннотацию FunctionalInterface, которая помогает понять компилятору, что интерфейс функциональный. Следовательно, не позволяет иметь более одного абстрактного метода. Тем не менее, мы можем её опустить.
В Java 7, функциональные интерфейсы рассматривались как Single Abstract Methods (SAM). SAM обычно реализовывались с помощью анонимных классов.
Пример 2: реализация SAM с помощью анонимного класса в java
В этом примере, мы принимаем анонимный класс для вызова метода. Это помогало писать программы с меньшим количеством строк кода в Java 7. Однако, синтаксис оставался достаточно сложным и громоздким.
Java 8 расширила возможности SAM, сделав шаг вперед. Как мы знаем, функциональный интерфейс содержит только один метод, следовательно, нам не нужно указывать название метода при передаче его в качестве аргумента. Именно это и позволяет нам lambda-выражения.
Введение в лямбда-выражения
Лямбда-выражения, по сути, это анонимный класс или метод. Лямбда-выражение не выполняется само по себе. Вместо этого, оно используется для реализации метода, определенного в функциональном интерфейсе.
Как записать лямбда-выражение в Java?
В Java, лямбда-выражения имеют следующий синтаксис:
Здесь мы использовали новый оператор (->) — лямбда-оператор. Возможно, синтаксис кажется немного сложным. Давайте разберем пару примеров.
Предположим, у нас есть такой метод:
Мы можем записать его, используя лямбда, как:
Этот метод не имеет никаких параметров. Следовательно, левая часть выражения содержит пустые скобки. Правая сторона – тело лямбда-выражения, которое определяет его действие. В нашем случае, возвращается значение 3.1415.
Типы лямбда-выражений
В Java, тело лямбды может быть двух типов.
2. Блочные (многострочные)
Этот тип позволяет лямбда-выражению иметь несколько операций внутри себя. Эти операции должны быть помещены в фигурные скобки, после которых необходимо ставить точку с запятой.
Примечание: многострочные лямбда-выражения, всегда должны иметь оператор return, в отличии от однострочных.
Пример 3: лямбда-выражение
Давайте напишем Java программу, которая бы возвращала значение Pi, используя лямбда-выражение.
Как говорилось ранее, лямбда-выражение не выполняется само собой. Скорее, оно формирует реализацию абстрактного метода, объявленного в функциональном интерфейсе.
И так, для начала, нам необходимо описать функциональный интерфейс.
Лямбда-выражения с параметрами
До этого момента, мы создавали лямбда-выражения без каких-либо параметров. Однако, как и методы, лямбды могут иметь параметры.
В этом примере, переменная n внутри скобок является параметром, переданном в лямбда-выражение. Тело лямбды принимает параметр и проверяет его на четность.
Пример 4: использование лямбда-выражения с параметрами
Параметризированный функциональный интерфейс
До этого момента, мы использовали функциональные интерфейсы, которые принимали только один тип значения. Например:
Вышеупомянутый функциональный интерфейс принимает только String и возвращает String. Однако, мы можем сделать наш интерфейс универсальным, чтобы использовать с любым типом данных.
Пример 5: параметризированный интерфейс и лямбда-выражения
В этом примере, мы создали параметризированный функциональный интерфейс GenericInterface, который содержит параметризированный метод func().
Затем, внутри класса Main:
Лямбда-выражения и Stream API
В JDK8 добавлен новый пакет java.util.stream, который позволяет java-разработчикам выполнять такие операции, как поиск, фильтрация, сопоставление, объединение или манипулирование коллекциями, к примеру Lists.
Например, у нас есть поток данных (в нашем случае список строк), где каждая строка содержит название страны и ее город. Теперь мы можем обработать этот поток данных и выбрать только города Непала.
Для этого мы можем использовать комбинацию Stream API и лямбда-выражений.
Пример 6: использование лямбд в Stream API
В приведенном выше примере обратите внимание на это выражение:
Здесь мы используем такие методы, как filter(), map(), forEach() из Stream API, которые могут принимать лямбды в качестве параметра.
Также, мы можем описать собственные выражения на основе синтаксиса, описанного выше. Это позволит нам уменьшить количество строк кода.
BestProg
Java. Лямбда-выражения. Основные понятия. Функциональный интерфейс. Обобщенные функциональные интерфейсы и лямбда-выражения. Примеры
Содержание
Поиск на других ресурсах:
1. Понятие о лямбда-выражении. Преимущества применения лямбда-выражений
Лямбда-выражения появились в версии JDK 8 с целью усовершенствования языка Java. Лямбда-выражения еще называют замыканиями.
К преимуществам применения лямбда-выражений в языке Java можно отнести следующие:
Появление лямбда-выражений способствовала возникновению таких новых средств языка Java, как методы по умолчанию и использование ссылок на метод без его выполнения.
Реализация любого лямбда-выражения базируется на использовании двух языковых конструкций:
Лямбда-выражение — это анонимный (безымянный) метод, который не выполняется самостоятельно, а служит реализацией метода, объявленного в функциональном интерфейсе (смотрите пункт 3). Если в программе встречается лямбда-выражение, то это приводит к созданию некоторого анонимного класса, содержащего анонимный метод, код которого определяется в лямбда-выражении.
При объявлении лямбда-выражения используется лямбда-оператор, который обозначается символами –> (стрелка). Лямбда-оператор трактуется как «становится» или «переходит». Лямбда-оператор ( –> ) разделяет лямбда-выражение на две части: левую и правую. В левой части лямбда-выражения указываются параметры. В правой части указываются действия (операторы), которые определяют код лямбда-выражения.
Код лямбда-выражения может формироваться одним из двух способов:
В простейшем случае, объявление блочного лямбда-выражения может быть следующим:
Если в лямбда-выражении содержится единственный параметр, он может быть без круглых скобок ( ) :
Возможна ситуация, когда лямбда-выражение не получает параметров. В этом случае общая форма блочного лямбда-выражения следующая
Если в лямбда-выражении используется один оператор (выражение), то фигурные скобки можно опустить:
Пример 1. Ниже приведено лямбда-выражение без параметров, которое возвращает число π :
Пример 2. Лямбда-выражение, которое получает два целочисленных параметра и возвращает их произведение.
3. Функциональный интерфейс. Определение. Общая форма. Примеры
Функциональный интерфейс — это интерфейс, который содержит только один абстрактный метод. Функциональный интерфейс определяет только одно действие (операцию). В функциональном интерфейсе определяется целевой тип лямбда-выражения. Функциональный интерфейс еще называют SAM-типом (Single Abstract Method).
В наиболее общем случае объявления функционального интерфейса выглядит следующим образом:
Если в интерфейсе объявляется два и более абстрактных метода, то этот интерфейс не считается функциональным интерфейсом.
Пример 1. Объявляется функциональный интерфейс, который определяет абстрактный метод, не получающий параметров и возвращающий целое число
Код лямбда-выражения, реализующий данный интерфейс, может быть, например, следующим:
После формирования лямбда-выражения можно вызвать метод GetNumber() интерфейса INumber()
Пример 2. Объявляется функциональный интерфейс, который определяет метод, получает 3 числа типа double и возвращает значение типа double
Для такой сигнатуры метода можно реализовывать лямбда-выражения, которые выполняют различные операции над тремя числами, например:
Ниже приведено построение и использование лямбда-выражения, вычисляющего сумму трех чисел
4. Последовательность шагов при построении лямбда-выражений
Для того, чтобы в программе использовать лямбда-выражения нужно выполнить следующие шаги:
5. Решение задач на лямбда-выражения
5.1. Лямбда-выражение, которое обрабатывает три числа
Условие задачи. Создать и вызвать лямбда-выражение, которое вычисляет:
Задачу реализовать с использованием функционального интерфейса.
Решение. Текст программы следующий.
Результат выполнение программы
5.2. Лямбда-выражение, которое используется в параметре метода для обработки массива чисел
Реализовать и продемонстрировать метод, который находит сумму парных и непарных элементов целочисленного массива. Метод должен получать параметром лямбда-выражение, которое определяет четность элемента и использовать его для вычислений. Продемонстрировать работу метода для лямбда-выражений, которые:
Результат выполнения программы
В вышеприведенном коде можно использовать ссылку на метод по образцу:
В программе передача метода IsOdd() в метод Sum() будет выглядеть следующим образом:
5.3. Лямбда-выражение, использующее шаблон (обобщение)
Создать и вызвать лямбда-выражение используя шаблон (обобщение). Лямбда-выражение должно находить максимум из трех цифр.
5.4. Решение квадратного уравнения с помощью лямбда-выражения
В задаче демонстрируется использование лямбда-выражения для решения квадратного уравнения. Программа содержит следующие составляющие:
Лямбда-выражения в Java 8 — Простые примеры того, как они могут облегчить вашу жизнь
Вот и состоялся релиз Java 8. Кто-то по-настоящему ждал её и тестировал предрелизную версию, считая недели до марта, для кого-то смена цифры в версии JDK была лишь поводом пару раз поиграть с обновленным языком в домашней IDE без отрыва от работы (ввод языка в production всегда занимает некоторое время), кто-то просто не нуждается в новых фичах, им и возможностей «семерки» хватает с лихвой. Тем не менее, восьмую Java ждать стоило — и не просто ждать, но и внимательно присмотреться к некоторым ее нововведениям, ведь в этой версии их действительно немало, и если ознакомиться с ними поближе, то не исключено, что хорошо знакомый язык предстанет перед вами в совершенно новом свете, порадовав возможностью писать еще более красивый и лаконичный код. И если уж говорить про новые возможности Java 8, было бы странно не начать с лямбда-выражений.
Так уж получилось, что в последние годы Oracle было сложно «обвинить» в быстром или революционном развитии языка — пока конкуренты по цеху обрастали новыми фичами чуть ли не ежегодно, в Java неспешно фиксили баги и выкатывали essentials, иногда — с некоторым опозданием. Так вышло и с лямбда-выражениями. Слухи о них ходили еще до седьмой версии, но не срослось, были написаны не одни «костыли», отчасти решавшие эту проблему, потом многие обрели для себя «Джаву с функциональщиной» в виде Scala и более-менее успокоились, потом Java 8 пару раз отложили — и вот, наконец, все желающие дождались официальной поддержки лямбд.
Тем не менее, несмотря на определенный интерес к теме среди разработчиков, многие до сих пор не совсем понимают, зачем оно нужно, как это использовать, и что это вообще за функциональное программирование, которое так и норовит наступить на пятки прочим парадигмам. Поскольку каждый из этих вопросов достоин как минимум солидной статьи (скорее даже книги), в этом посте будут рассмотрены лишь некоторые аспекты применения лямбда-выражений на практике, на максимально простых и понятных примерах. Стоит сразу предупредить дорогих читателей, что в первую очередь этот пост написан для того, чтобы заинтересовать Java-разработчиков темой лямбд в новой версии языка (если они по какой-то причине про них еще не знали или не оценили их по достоинству), не раздувая его за счет подробностей и не пугая их на этом этапе заумными терминами функциональщины. Поэтому если вы уже имеете опыт работы с функциональной парадигмой, то скорее всего, в этой статье вы не найдете почти ничего нового. Всем остальным, а особенно Java-разработчикам, учившим Java еще пару версий назад в ее первозданном варианте (без функциональных «костылей» вроде lambdaj), возможно будет интересно.
Отучаемся писать много
Проще, наверное, и быть не может — создадим список чисел и выведем его на экран через простейший цикл:
Каждый человек, использовавший Java, наверняка писал такие циклы — казалось бы, невозможно придумать что-то более простое и удобное. Или всё-таки возможно? Что происходит в цикле? Все мы знаем ответ — числа выводятся на экран одна за другой, пока не достигнут конец списка. Звучит вполне логично и правильно, не так ли? Но давайте посмотрим на проблему с другой стороны. Тут на ум приходит сравнение с человеком, который складывает детали от конструктора «Лего», разбросанные по полу, в одну коробку — его цикл заключается в том, что он берет одну детальку, кладет в коробку, смотрит, не осталось ли на полу других деталей (их там не одна сотня), кладет в коробку следующую деталь, снова проверяет, не остались ли еще детали, снова кладет, снова проверяет. Но что мешает взять в охапку столько деталей, сколько сможешь ухватить, и разом закинуть их в коробку?
Попробуем отойти от старых привычек и таки наконец воспользоваться лямбдами. Вот как может выглядеть тот же код в Java 8:
Как мы можем видеть, структура лямбда-выражения может быть разделена на две части: часть слева от «стрелки» (->) содержит параметры выражения, а часть справа — его «тело». Компилятор уже знает, как ему работать с этим выражением, более того — в большинстве случаев, типы в лямбда-выражениях можно не указывать в коде явным образом, делая выражение еще более лаконичным:
Но и это не предел — можно использовать оператор :: и получить еще более красивое:
Не знаю, как вам, но мне после этого писать на работе циклы «по-старинке» уже совсем не хочется.
Копипаст или абстракции? Выбирать вам!
Но, конечно, вокруг функциональной парадигмы не было бы столько шума, если бы она была нужна только для вывода на экран нескольких чисел одной строчкой кода. Если хотя бы ненадолго задуматься об их применении, то окажется, что дело еще и в повышении уровня абстракции и гибкости ваших проектов. Давайте разберем еще один пример:
Уже знакомый нам список, но теперь представим, что для проекта (своего или рабочего) требуется написать метод, который находит сумму всех элементов списка (Эх, если бы задачи действительно были такими простыми!)
Просто? Безусловно. Хорошо, представим, прошло некоторое время, и оказалось, что нужно написать еще один метод — пусть он, например, складывает только четные числа. Тоже задача уровня 10-го класса, не так ли?
Всё прекрасно, но количество задач растет, и в один день нам понадобился еще один метод, который находит сумму всех чисел больше 3-х. Уверен, что первое, что придет в голову большинству разработчиков — это выделить предыдущий метод, воспользоваться старым добрым копипастом и поменять условие. Прекрасно, код работает, но… Самый ли это логичный подход? Представим, что новые методы придется дописывать постоянно, и завтра нам понадобится метод, считающий суммы нечетных чисел, чисел больше 2, меньше 5, и так далее. В итоге даже задачки школьного уровня вырастут в целую «простыню» кода. Неужели в 2014-м году нет более простого подхода?
Было бы странно, если бы его не было. Воспользуемся еще одной фичей Java 8 — функциональным интерфейсом Predicate, который определяет, как мы будем сортировать наши числа до того, как суммировать их.
В таком случае, саму реализацию всех возможных вариантов мы можем уместить всего в 3 строчки:
Красота же! И это видно всего на двух простейших примерах — не углубляясь в дебри, не затрагивая lazy evaluation и прочие важные аспекты. Впрочем, целью этой статьи как раз и было заинтересовать Java-разработчиков темой лямбд в новой версии языка (если они по какой-то причине до этого момента не обращали на них внимания), не перегружая читателя на этом этапе матчастью и всей глубиной идей ФП.
Если кого-то эти практические примеры вдохновят на то, чтобы найти книги и блоги по теме, начав путь по постижению дзена функционального программирования (путь, конечно, не самый короткий, но ведь красота она такая — требует жертв), то это уже будет очень хорошо — как для вас, так и для меня, ведь получается, что не зря писал. К тому же, уверен, на этом пути вы со временем найдете и более удачные примеры, которые выглядят намного любопытнее тех, что представлены в этом посте. Спасибо за внимание!