Java лямбда выражения примеры

Java лямбда выражения примеры

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

Среди новшеств, которые были привнесены в язык Java с выходом JDK 8, особняком стоят лямбда-выражения. Лямбда представляет набор инструкций, которые можно выделить в отдельную переменную и затем многократно вызвать в различных местах программы.

Основу лямбда-выражения составляет лямбда-оператор , который представляет стрелку -> . Этот оператор разделяет лямбда-выражение на две части: левая часть содержит список параметров выражения, а правая собственно представляет тело лямбда-выражения, где выполняются все действия.

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

В роли функционального интерфейса выступает интерфейс Operationable , в котором определен один метод без реализации — метод calculate . Данный метод принимает два параметра — целых числа, и возвращает некоторое целое число.

По факту лямбда-выражения являются в некотором роде сокращенной формой внутренних анонимных классов, которые ранее применялись в Java. В частности, предыдущий пример мы можем переписать следующим образом:

Чтобы объявить и использовать лямбда-выражение, основная программа разбивается на ряд этапов:

Определение ссылки на функциональный интерфейс:

Причем параметры лямбда-выражения соответствуют параметрам единственного метода интерфейса Operationable, а результат соответствует возвращаемому результату метода интерфейса. При этом нам не надо использовать ключевое слово return для возврата результата из лямбда-выражения.

Так, в методе интерфейса оба параметра представляют тип int , значит, в теле лямбда-выражения мы можем применить к ним сложение. Результат сложения также представляет тип int , объект которого возвращается методом интерфейса.

Использование лямбда-выражения в виде вызова метода интерфейса:

Так как в лямбда-выражении определена операция сложения параметров, результатом метода будет сумма чисел 10 и 20.

При этом для одного функционального интерфейса мы можем определить множество лямбда-выражений. Например:

Отложенное выполнение

Одним из ключевых моментов в использовании лямбд является отложенное выполнение (deferred execution). То есть мы определяем в одном месте программы лямбда-выражение и затем можем его вызывать при необходимости неопределенное количество раз в различных частях программы. Отложенное выполнение может потребоваться, к примеру, в следующих случаях:

Выполнение кода отдельном потоке

Выполнение одного и того же кода несколько раз

Выполнение кода в результате какого-то события

Выполнение кода только в том случае, когда он действительно необходим и если он необходим

Передача параметров в лямбда-выражение

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

Если метод не принимает никаких параметров, то пишутся пустые скобки, например:

Если метод принимает только один параметр, то скобки можно опустить:

Терминальные лямбда-выражения

Выше мы рассмотрели лямбда-выражения, которые возвращают определенное значение. Но также могут быть и терминальные лямбды, которые не возвращают никакого значения. Например:

Лямбды и локальные переменные

Лямбда-выражение может использовать переменные, которые объявлены во вне в более общей области видимости — на уровне класса или метода, в котором лямбда-выражение определено. Однако в зависимости от того, как и где определены переменные, могут различаться способы их использования в лямбдах. Рассмотрим первый пример — использования переменных уровня класса:

Переменные x и y объявлены на уровне класса, и в лямбда-выражении мы их может получить и даже изменить. Так, в данном случае после выполнения выражения изменяется значение переменной x.

Теперь рассмотрим другой пример — локальные переменные на уровне метода:

Локальные переменные уровня метода мы также может использовать в лямбдах, но изменять их значение мы уже не сможем. Если мы попробуем это сделать, то среда разработки (Netbeans) может нам высветить ошибку и то, что такую переменную надо пометить с помощью ключевого слова final , то есть сделать константой: final int n=70; . Однако это необязательно.

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

Блоки кода в лямбда-выражениях

Существуют два типа лямбда-выражений: однострочное выражение и блок кода. Примеры однострочных выражений демонстрировались выше. Блочные выражения обрамляются фигурными скобками. В блочных лямбда-выражениях можно использовать внутренние вложенные блоки, циклы, конструкции if, switch, создавать переменные и т.д. Если блочное лямбда-выражение должно возвращать значение, то явным образом применяется оператор return :

Обобщенный функциональный интерфейс

Функциональный интерфейс может быть обобщенным, однако в лямбда-выражении использование обобщений не допускается. В этом случае нам надо типизировать объект интерфейса определенным типом, который потом будет применяться в лямбда-выражении. Например:

Таким образом, при объявлении лямбд-выражения ему уже известно, какой тип параметры будут представлять и какой тип они будут возвращать.

В этой статье речь идет о лямбда-выражениях, функциональных интерфейсах. Подробно рассматривается синтаксис лямбда-выражений, а также приводятся стандартные функциональные интерфейсы из JDK.

Пример

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

Первый метод простой – выдача всех адресов. Но другие методы сложнее, например:

Каждый раз надо придумывать новые имена, писать новые методы. Может это и не так сложно, но суть в том, что можно сделать проще с помощью лямбда-выражений.

Читайте также:  A1524 iphone 6 plus

Именно здесь и можно применить лямбда-выражение – это блок кода, описывающий функцию интерфейса; функция эта передается как параметр в метод и вызывается при надобности (в нашем случае в тот момент, когда надо проверить условие см. функцию filter.test()).

В нашем случае код с лямбда-выражением будет выглядеть так, что вместо четырех методов появится один, принимающий условие как аргумент:

А уж при вызове метода мы будем передавать лямбда-выражение:

Выше мы получили все электронные адреса.

Далее получим адреса, начинающиеся с буквы “a”:

Аналог – анонимный класс

Лямбда выражения – это не что-то принципиально новое, появившееся в Java 8, а просто синтаксический сахар для анонимного класса. Раньше бы вместо вышеприведенного лямбда-выражения в параметр бы передавалась вот такая громоздкая конструкция:

Выглядит страшно, поэтому распространение получили такие вещи с Java 8 именно благодаря сахару.

Немного о синтаксисе

Пора сказать пару слов о синтаксисе лямбда-выражения. По сути лямбда-выражение – это метод интерфейса, причем его единственный метод. Слева от стрелки “->” передается список аргументов метода, а справа – сам текст метода.

В аргументе getEmail() мы передаем интерфейс Predicate. Но как компилятор определяет, какой именно метод интерфейса передается?

Немного о функциональных интерфейсах

Predicate – это функциональный интерфейс, что означает, что он состоит только из одного метода (не считая статических и методов по умолчанию). Этот метод и вызывается для тестирования нашего условия. Мы можем передавать непосредственно содержимое этого метода, не указывания его имени. Компилятор и так догадается, что это за метод, поскольку в интерфейсе Predicate он всего один:

Если бы из было два, то компилятор выдал бы ошибку. Лямбда-выражение можно передавать, только если оно реализует метод функционального интерфейса.

Таким образом, компилятор понимает, что email->email.startsWith(“a”) – это реализация метода test() интерфейса Predicate.

Еще раз: поскольку метод единственный, и не надо уточнять его название, можно передать в параметр типа Predicate саму реализацию метода и аргумент. Idea сама предлагает заменить такой навороченный участок кода кратким лямбда-выражением.

Допишем остальные методы

Допишем вызовы с лямбда-выражениями оставшихся двух методов.

Этот получает электронные адреса из группы1:

А этот получает электронные адреса на букву “а” из группы1:

Подробнее о функциональных интерфейсах

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

Вот примеры, угадайте является ли следующий интерфейс функциональным:

Ответ нет, в интерфейсе Movable нет ни одного абстрактного метода, а надо один.

Ответ да, в интерфейсе Runnable ровно один метод (из Movable не наследуется ни одного).

Интерфейс FastRunnable функциональный, поскольку он наследует один метод из Runnable, а default-метод fastrun() не учитывается.

Интерфейс Swimming не является функциональным, поскольку из Movable методы не наследуются, а метод ss() статический, а значит не учитывается.

Например, Runnable – функциональный, а значит можно поставить аннотацию @FunctionalInterface без проблем:

Подробнее о синтаксисе лямбда-выражения

Вернемся к примеру с электронными адресами. В нем среди прочих мы использовали такое лямбда-выражение:

Выглядит оно не как полноценная функция, потому что в лямбда-выражених многие части опускаются. Вышеприведенный код эквивалентен следующему:

Слева от стрелки параметры, а справа тело метода.

Параметр метода здесь один, он имеет тип String. Возвращаемое значение тоже имеет тип String.

Это значит, что данное лямбда-выражение подойдет к любому функциональному интерфейсу, имеющему метод с одним параметром типа String и возвращающим значение такого же типа. Например, вместо встроенного в JDK 8 интерфейса Predicate мы могли бы определить и использовать любой собственный интерфейс:

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

Когда можно опустить круглые скобки

Рассмотрим внимательнее левую часть двух вышеприведенных выражений:

Краткая формаё

А это полная форма того же лямбда-выражения:

Полная форма

Во втором выражении присутствует тип аргумента и круглые скобки, а в первом нет.

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

Вот примеры корректно написанных лямбда-выпажений:

А вот примеры некорректных лямбда-выражений:

  1. В первом выражении задан тип, а значит нужны скобки.
  2. Во втором аргументов два, а не один, что означает, снова нужны скобки.
  3. В третьем снова два аргумента, а не один.

Фигурные скобки справа

Теперь сравним правую часть выражений, во втором выражении есть фигурные скобки <>.

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

Как уже понятно, в первом выражении все это опущено потому, что одна строка кода позволяет все это опустить.

Приведем еще примеры корректный лямбда-выражений:

Найдите ошибки в лямбда-выражениях

А теперь некорректные, угадайте, что с ними не так:

  1. В первом нужны круглые скобки слева, поскольку аргументов два.
  2. Во втором – фигурные скобки справа.
  3. В третьем пропущена точка с запятой (справа).

Привильный вариант такой:

А почему некорректны вот эти выражения, еще не упоминалось:

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

Чтобы сделать выражения корректными, надо либо убрать все типы (компилятор обычно их может определить из кода, не переживайте), либо, наоборот, указать все типы:

Читайте также:  Блок питания ps 1502dd схема

Вот еще некорретное выражение:

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

Вот так будет правильно:

Ссылки на методы (Method references)

Но это еще не все, синтаксический сахар простирается дальше.

Он имеет такую же сигнатуру, как метод стандартного функционального интерфейса Consumer из jdk, а именно один параметр и возвращаемый тип void:

Поэтому, если нам надо что-то просто напечатать в функции типа Consumer, то вместо такой записи:

можно использовать сокращенную:

Последняя запись эквивалентна предыдущей.

Поскольку сигнатуры нашего Consumer и println() одинаковы, а дополнительных действий в теле метода (1) нет, то аргумент a слева и справа упоминать избыточно, так как мы его просто передаем дальше.

Стандартные функциональные интерфейсы и примеры их использования в JDK 8

Если вы хотите передать в параметр функцию, то наверняка свой функциональный интерфейс писать не придется, так как уже есть куча стандартных интерфейсов с различными сигнатурами функций.

В пакете java.util.function их целых 43.

Но запомнить надо всего шесть, остальные интерфейсы производные от остальных. Например, мы выше использовали интерфейс Predicate, который возвращает boolean и принимает один аргумент. А есть BiPredicate – он такой же, но принимает два аргумента.

Вот эти шесть интерфейсов (в третьей колонке примеры с method reference, который мы рассматривали выше):

Интерфейс Сигнатура Пример из JDK
UnaryOperator T apply(T t) String::toLowerCase
BinaryOperator T apply(T t1, T t2) BigInteger::add
Predicate boolean test(T t) Collection::isEmpty
Function R apply(T t) Arrays::asList
Supplier T get() Instant::now
Consumer void accept(T t) System.out::println

А название из первого поясняет суть той или иной сигнатуры.

Например, BigInteger::add – бинарный оператор.

А вот (почти) все интерфейсы из java.util.function, которые поместились на скриншот:

java.util.function

Проще зайти и посмотреть их сигнатуру, чем перечислять тут. Но они производятся по таким принципам:

  • Есть дополнительные три интерфейса BiFunction, BiPredicate, BiConsumer, принимающие два аргумента, а не один, в отличие от исходных Function, Predicate, Consumer. Пример:
  • Для всех интерфейсов, где это имеет смысл, есть производные с префиксом Double, Int, Long – они принимают конкретный примитивный тип, а не Generic, как исходные например:
  • Для интерфейса Function помимо производных, уточняющих примитивный тип аргумента, есть производные с ..To.., уточняющие возвращаемый примитивный тип, например:
  • Особняком стоит BooleanSupplier, возвращающий boolean:

Замыкания в Java

Еще один интересный (но не рекомендуемый) способ использования лямбда-выражений – это замыкания. Те, кто знает JavaScript, понимают о чем речь. Возьмем пример:

К удивлению, результат в консоли будет таким:

На первый взгляд кажется магией, ведь локальные переменные в методах живут только во время метода, а потом забываются. А тут arr[] определенно запоминается.

Но на самом деле arr[] находится не в методе, а в синтетическом final поле некоторого класса, который генерируется jvm и содержит нашу функцию () -> ++arr[0]; Так что все нормально.

Итоги

Пока итогов нет, статья будет дополняться. Исходный код некоторых примеров доступен на GitHub.

Вот и состоялся релиз Java 8. Кто-то по-настоящему ждал её и тестировал предрелизную версию, считая недели до марта, для кого-то смена цифры в версии JDK была лишь поводом пару раз поиграть с обновленным языком в домашней IDE без отрыва от работы (ввод языка в production всегда занимает некоторое время), кто-то просто не нуждается в новых фичах, им и возможностей «семерки» хватает с лихвой. Тем не менее, восьмую Java ждать стоило — и не просто ждать, но и внимательно присмотреться к некоторым ее нововведениям, ведь в этой версии их действительно немало, и если ознакомиться с ними поближе, то не исключено, что хорошо знакомый язык предстанет перед вами в совершенно новом свете, порадовав возможностью писать еще более красивый и лаконичный код. И если уж говорить про новые возможности Java 8, было бы странно не начать с лямбда-выражений.

Так уж получилось, что в последние годы Oracle было сложно «обвинить» в быстром или революционном развитии языка — пока конкуренты по цеху обрастали новыми фичами чуть ли не ежегодно, в Java неспешно фиксили баги и выкатывали essentials, иногда — с некоторым опозданием. Так вышло и с лямбда-выражениями. Слухи о них ходили еще до седьмой версии, но не срослось, были написаны не одни «костыли», отчасти решавшие эту проблему, потом многие обрели для себя «Джаву с функциональщиной» в виде Scala и более-менее успокоились, потом Java 8 пару раз отложили — и вот, наконец, все желающие дождались официальной поддержки лямбд.

Тем не менее, несмотря на определенный интерес к теме среди разработчиков, многие до сих пор не совсем понимают, зачем оно нужно, как это использовать, и что это вообще за функциональное программирование, которое так и норовит наступить на пятки прочим парадигмам. Поскольку каждый из этих вопросов достоин как минимум солидной статьи (скорее даже книги), в этом посте будут рассмотрены лишь некоторые аспекты применения лямбда-выражений на практике, на максимально простых и понятных примерах. Стоит сразу предупредить дорогих читателей, что в первую очередь этот пост написан для того, чтобы заинтересовать Java-разработчиков темой лямбд в новой версии языка (если они по какой-то причине про них еще не знали или не оценили их по достоинству), не раздувая его за счет подробностей и не пугая их на этом этапе заумными терминами функциональщины. Поэтому если вы уже имеете опыт работы с функциональной парадигмой, то скорее всего, в этой статье вы не найдете почти ничего нового. Всем остальным, а особенно Java-разработчикам, учившим Java еще пару версий назад в ее первозданном варианте (без функциональных «костылей» вроде lambdaj), возможно будет интересно.

Читайте также:  Asus p7q57 m do характеристики

Отучаемся писать много

Проще, наверное, и быть не может — создадим список чисел и выведем его на экран через простейший цикл:

Каждый человек, использовавший Java, наверняка писал такие циклы — казалось бы, невозможно придумать что-то более простое и удобное. Или всё-таки возможно? Что происходит в цикле? Все мы знаем ответ — числа выводятся на экран одна за другой, пока не достигнут конец списка. Звучит вполне логично и правильно, не так ли? Но давайте посмотрим на проблему с другой стороны. Тут на ум приходит сравнение с человеком, который складывает детали от конструктора «Лего», разбросанные по полу, в одну коробку — его цикл заключается в том, что он берет одну детальку, кладет в коробку, смотрит, не осталось ли на полу других деталей (их там не одна сотня), кладет в коробку следующую деталь, снова проверяет, не остались ли еще детали, снова кладет, снова проверяет. Но что мешает взять в охапку столько деталей, сколько сможешь ухватить, и разом закинуть их в коробку?

Попробуем отойти от старых привычек и таки наконец воспользоваться лямбдами. Вот как может выглядеть тот же код в Java 8:

Как мы можем видеть, структура лямбда-выражения может быть разделена на две части: часть слева от «стрелки» (->) содержит параметры выражения, а часть справа — его «тело». Компилятор уже знает, как ему работать с этим выражением, более того — в большинстве случаев, типы в лямбда-выражениях можно не указывать в коде явным образом, делая выражение еще более лаконичным:

Но и это не предел — можно использовать оператор :: и получить еще более красивое:

Не знаю, как вам, но мне после этого писать на работе циклы «по-старинке» уже совсем не хочется.

Копипаст или абстракции? Выбирать вам!

Но, конечно, вокруг функциональной парадигмы не было бы столько шума, если бы она была нужна только для вывода на экран нескольких чисел одной строчкой кода. Если хотя бы ненадолго задуматься об их применении, то окажется, что дело еще и в повышении уровня абстракции и гибкости ваших проектов. Давайте разберем еще один пример:

Уже знакомый нам список, но теперь представим, что для проекта (своего или рабочего) требуется написать метод, который находит сумму всех элементов списка (Эх, если бы задачи действительно были такими простыми!)

Просто? Безусловно. Хорошо, представим, прошло некоторое время, и оказалось, что нужно написать еще один метод — пусть он, например, складывает только четные числа. Тоже задача уровня 10-го класса, не так ли?

Всё прекрасно, но количество задач растет, и в один день нам понадобился еще один метод, который находит сумму всех чисел больше 3-х. Уверен, что первое, что придет в голову большинству разработчиков — это выделить предыдущий метод, воспользоваться старым добрым копипастом и поменять условие. Прекрасно, код работает, но… Самый ли это логичный подход? Представим, что новые методы придется дописывать постоянно, и завтра нам понадобится метод, считающий суммы нечетных чисел, чисел больше 2, меньше 5, и так далее. В итоге даже задачки школьного уровня вырастут в целую «простыню» кода. Неужели в 2014-м году нет более простого подхода?

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

В таком случае, саму реализацию всех возможных вариантов мы можем уместить всего в 3 строчки:

Красота же! И это видно всего на двух простейших примерах — не углубляясь в дебри, не затрагивая lazy evaluation и прочие важные аспекты. Впрочем, целью этой статьи как раз и было заинтересовать Java-разработчиков темой лямбд в новой версии языка (если они по какой-то причине до этого момента не обращали на них внимания), не перегружая читателя на этом этапе матчастью и всей глубиной идей ФП.
Если кого-то эти практические примеры вдохновят на то, чтобы найти книги и блоги по теме, начав путь по постижению дзена функционального программирования (путь, конечно, не самый короткий, но ведь красота она такая — требует жертв), то это уже будет очень хорошо — как для вас, так и для меня, ведь получается, что не зря писал. К тому же, уверен, на этом пути вы со временем найдете и более удачные примеры, которые выглядят намного любопытнее тех, что представлены в этом посте. Спасибо за внимание!

Ссылка на основную публикацию
Insomnia the ark цена свободы
Очередь просмотра Очередь Удалить все Отключить YouTube Premium Хотите сохраните это видео? Пожаловаться Пожаловаться на видео? Выполните вход, чтобы сообщить...
High speed hdmi cable with ethernet
Приветствую всех посетителей блога о компьютерах. Сегодня бы очень хотелось поведать читателям о так называемом HDMI кабеле, с помощью которого...
Highscreen pure j 4pda прошивка
Внимание! Использование инструментов для перепрошивки неопытными пользователями может привести к выходу аппарата из строя. Установка этого обновления приведет к полному...
Instagram как выложить фото с компьютера
Зачем нужно выкладывать фото и видео в Инстаграм через компьютер? Инстаграм придумали, чтобы можно было быстро загрузить фото с телефона....
Adblock detector