No Image

Что такое лямбда выражение в java

СОДЕРЖАНИЕ
0 просмотров
10 марта 2020

Каждый разработчик должен научиться использовать новые функции программирования, особенно теперь, когда 8-я версия достигла максимального пика использования. Лямбда-выражения — это новая и важная функция, включенная в Java 8. Она обеспечивает четкий и сжатый способ представления метода, который улучшает Collection библиотеки, упрощая итерацию, фильтрацию и извлечение данных. Новые возможности параллелизма улучшают производительность в многоядерных средах.

Принцип лямбда-выражений

Несколько лет назад специалистами обсуждались расширения языка Java для функционального программирования. В 2009 году стало ясно, что Java без lambdas будет выглядеть устаревшим по сравнению с другими языками программирования, поскольку современное многопроцессорное и многоядерное оборудование требует простой поддержки для распараллеливания программ. Вот зачем нужны лямбда-выражения.

В Java все коллекции имеют метод forEach, наследуемый от своего супер-интерфейса Iterable. Он существует со времен Java 5, был расширен до 8-й версии. Метод forEach выполняет итерацию по всем элементам коллекции и применяет функцию к каждому элементу.

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

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

  1. Элемент слева от стрелки (->) — это параметры лямбда. В этом случае входной параметр определяется, как String param.
  2. Справа от стрелки (->) — тело лямбды.
  3. Тело — это место, где происходит фактическая обработка лямбда, т. е. определяет ее логику. Обычно лямбда-выражение Java имеет простую логику.

Синтаксических опции для списка параметров

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

  1. LambdaExpression.
  2. LambdaParameters ‘->’ LambdaBody LambdaParameters.
  3. Identifier ‘(‘ ParameterList ‘)’ LambdaBody.
  4. Expression Block.

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

Список параметров с единственным параметром с явной спецификацией типа

Неверно, если указан тип параметра, используют круглые скобки

Список параметров с единственным параметром без явной спецификации типа, компилятор выводит отсутствующий тип параметра из контекста

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

(int x, int y) -> x + y

Список параметров с двумя параметрами n с явной спецификацией типа

int x, int y -> x + y

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

Список параметров с двумя параметрами без явной спецификации типа

Неверно, для более чем одного параметра необходимо использовать скобки

Неверно, нельзя смешивать параметры с типом и без него. Либо все имеют явную спецификацию типа, либо нет

Список может быть пустым

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

Тело, состоящее из одиночного выражения

-> (args! = null) ? args.length : 0

Оператор также возвращает одно выражение

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

Тело, при передачи лямбда-выражений.

(int x) -> return x + 1

Неверно, с возвратом начинается утверждение, и лямбда отсутствует, return должен заканчиваться точкой с запятой и находиться в скобках

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

  • В правой части присвоений;
  • в роли аргументов метода при вызове;
  • в качестве значения в операторе возврата;
  • в цельном значении.

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

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

Для всех функциональных интерфейсов рекомендуется иметь информативную аннотацию. Это не только четко передает его цель, но также позволяет компилятору генерировать ошибку, если аннотированный интерфейс не удовлетворяет условиям SAM. Интерфейс с Single Abstract Method является функциональным, и его реализация может рассматриваться, перед тем как использовать лямбда-выражения в Java 8.

Методы изначально не абстрактны и не учитываются, а интерфейс может по-прежнему иметь несколько методов по умолчанию. Можно наблюдать это, глядя на документацию Function. Самый простой и общий случай лямбда — это функциональный интерфейс с методом, который получает одно значение, возвращая другое. Эта функция одного аргумента представлена интерфейсом Function, который параметризуется типами аргументов и возвращаемыми значениями. Примеры-лямбда выражения Java:

Анонимный внутренний класс

В Java анонимные внутренние классы предоставляют способ реализации классов, встречающиеся в приложении только единожды. Например, в стандартном приложении Swing или JavaFX требуется несколько обработчиков событий для клавиатуры и мыши. Вместо того чтобы писать отдельный класс для каждого события, прописывают общую команду. Создавая класс на месте, где это необходимо, код становится читать намного проще.

Нельзя правильно понять, что такое лямбда выражения, не рассмотрев функциональные интерфейсы. Использование их с анонимными внутренними классами является общим шаблоном в Java. В дополнение к EventListener классам, такие интерфейсы, как Runnable, Comparator используются аналогичным образом. Поэтому функциональные интерфейсы используются конкретно с лямбда-выражениями.

Выражение анонимной функции

В основном, Lambda Expression — краткое представление анонимной функции, которая может быть передана. Таким образом, она обладает следующими свойствами:

  1. Анонимность, потому что у нее нет, как обычно, явного имени.
  2. Функциональность, ее можно рассматривать, как функцию, потому что лямбда не связана с определенным классом, как метод. Но аналогично методу, лямбда имеет список параметров, тело, тип возврата и список исключений, которые могут быть выбраны.
  3. Проход, выражение лямбда может передаваться, как аргумент методу или храниться в переменной.
  4. Краткость, не нужно писать много шаблонов, как для анонимных классов. Технически выражения Lambda позволяют делать то, что не делали до Java 8, кроме того теперь не нужно писать неуклюжий код, чтобы использовать параметризацию поведения.
  5. Список параметров. В этом случае он отражает параметры метода сравнения компаратора — два объекта Apple.
  6. Стрелка « ->» отделяет список параметров от тела лямбда.
  7. Выражение считается возвращаемым значением лямбда.

Синтаксис выражения

Лямбда-выражения определяют объемность анонимных внутренних классов путем преобразования 5 строк кода в один оператор. Это простое горизонтальное решение разрешает «вертикальную проблему», представленную внутренними классами. Лямбда-выражение состоит из 3 частей.

Тело может быть либо отдельным выражением, либо блоком оператора. В форме выражения тело просто оценивается и возвращается. В блочной форме тело оценивается, как тело метода, а оператор return возвращает управление вызывающей стороне анонимного метода. На верхнем уровне break continue ключевые слова являются незаконными, но разрешены внутри циклов. Если тело производит результат, каждый путь управления должен возвращать что-либо или генерировать исключение.

Первое выражение принимает два целочисленных аргумента, названных x и y, и использует форму выражения для возврата x + y. Второе не принимает аргументов и использует форму для возврата целого числа 42. Третье выражение принимает строку и использует форму блока для печати строки на консоли и ничего не возвращает.

Читайте также:  Выгрузка заказов из битрикс в 1с

Защита переменных объекта от мутации

Доступ к переменной в лямбда-выражениях приведет к ошибке времени компиляции. Но это не означает, что пользователь должен отмечать каждую целевую переменную как final. В соответствии с концепцией «фактически окончательный» компилятор рассматривает каждую переменную, как окончательную, если она назначается только один раз.

Безопасно использовать такие переменные внутри lambdas, потому что компилятор будет контролировать свое состояние и запускать ошибку времени компиляции сразу после любой попытки их изменить. Этот подход должен упростить процесс выполнения лямбда-исполнения. Одной из основных целей лямбда является использование в параллельных вычислениях — это означает, что они действительно полезны, когда дело касается безопасности потоков.

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

Захват переменной экземпляра

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

Нужно обратить внимание на ссылку this.name внутри лямбда-тела. Это фиксирует name переменную экземпляра EventConsumerImpl объекта. Можно даже изменить значение переменной экземпляра после ее захвата, и значение будет отражено внутри лямбда. Семантика this фактически является одной из областей, где Java lambdas отличается от анонимных реализаций интерфейсов. Реализация анонимного интерфейса может иметь свои собственные переменные экземпляра, на которые ссылается this ссылка. Тем не менее, лямбда не может иметь свои собственные переменные экземпляра, поэтому this всегда указывает на охватывающий объект.

Захват статической переменной Ямба-выражение Java также может захватывать статические переменные. Это неудивительно, так как статические переменные доступны везде, где есть приложение Java. Значение статической переменной также может измениться после того, как лямбда ее захватила. Класс в первую очередь служит для того чтобы показать, что лямбда может обращаться к статическим переменным.

Ссылки на методы

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

Поскольку тело лямбда состоит только из одного утверждения, можно фактически опустить прилагаемые < >скобки. Кроме того, поскольку для этого метода существует только один параметр, можно опустить прилагаемые ( ) скобки вокруг параметра. Вот как выглядит итоговое заявление лямбда: MyPrinter myPrinter = s -> System.out.println (s);

Поскольку все тело лямбда делает, пересылает строковый параметр в System.out.println() метод, можно заменить указанное выше lambda-объявление ссылкой на метод.

Вот как выглядит ссылка на лямбда-метод: MyPrinter myPrinter = System.out :: println.

Нужно обращать особое внимание на двойные двоеточия . Этот сигнал компилятору Java является ссылкой на метод. Указанный метод — это то, что происходит после двойных двоеточий. Независимо от класса или объекта, который владеет ссылочным методом.

Распространенные примеры использования

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

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

  1. Переменные объявления.
  2. Назначения операторов возврата.
  3. Инициализаторы массива.
  4. Аргументы метода или конструктора.
  5. Лямбда-выражения.
  6. Условные выражения.
  7. Выражение компаратор лямбда В Java.

Comparator класс используется для сортировки коллекций. В следующем примере выполняется сортировка ArrayList состоящего из Person объектов на основе surName. Ниже перечислены поля, включенные в Person класс. Lambda поддерживает «целевую типизацию», которая указывает тип объекта из контекста, в котором он используется. Поскольку присваивается результат Comparator определенному при помощи generic, компилятор может сделать вывод о том, что оба параметра имеют Person тип.

Рекомендации по предоставлению целевых типов

Метод является общим и абстрактным, что позволяет легко адаптироваться практически к любому лямбда. Разработчики должны изучить этот пакет перед созданием новых интерфейсов. Скачать лямбда-выражения в Java 8 pdf можно в Интернете.

В некотором классе UseFoo он принимает этот интерфейс как параметр. Если посмотреть выражение более внимательно, то можно увидите, что Foo — это не что иное, как функция, которая принимает один аргумент и дает результат. 8-я версия уже предоставляет такой интерфейс в функции. Можно полностью удалить интерфейс Foo и изменить код, прописывая выражения.

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

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

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

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

Однако пользователь, не должен использовать это правило «однострочной лямбды» в качестве догмы. Если у него есть две или три строки в определении лямбда, может оказаться нецелесообразным приводить их к одной строке. И также необходимо избегать указания типов параметров. Компилятор в большинстве случаев способен разрешать тип лямбда-параметров при помощи вывода типа, поэтому добавление типа к параметрам является необязательным и может быть опущено.

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

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

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

Читайте также:  Как добавить в трей значок

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

Среди новшеств, которые были привнесены в язык 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.

Пример

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

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

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

Именно здесь и можно применить лямбда-выражение – это блок кода, описывающий функцию интерфейса; функция эта передается как параметр в метод и вызывается при надобности (в нашем случае в тот момент, когда надо проверить условие см. функцию 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. В третьем пропущена точка с запятой (справа).

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

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

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

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

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

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

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

Ссылки на методы (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.

Комментировать
0 просмотров
Комментариев нет, будьте первым кто его оставит

Это интересно
No Image Компьютеры
0 комментариев
No Image Компьютеры
0 комментариев
No Image Компьютеры
0 комментариев
Adblock detector