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

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, которые могут принимать лямбды в качестве параметра.

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

Источник

Разбираем лямбда-выражения в Java

Что такое лямбда выражение и ссылка на метод java. Смотреть фото Что такое лямбда выражение и ссылка на метод java. Смотреть картинку Что такое лямбда выражение и ссылка на метод java. Картинка про Что такое лямбда выражение и ссылка на метод java. Фото Что такое лямбда выражение и ссылка на метод java

От переводчика: LambdaMetafactory, пожалуй, один из самых недооценённых механизмов Java 8. Мы открыли его для себя совсем недавно, но уже по достоинству оценили его возможности. В версии 7.0 фреймворка CUBA улучшена производительность за счет отказа от рефлективных вызовов в пользу генерации лямбда выражений. Одно из применений этого механизма в нашем фреймворке — привязка обработчиков событий приложения по аннотациям, часто встречающаяся задача, аналог EventListener из Spring. Мы считаем, что знание принципов работы LambdaFactory может быть полезно во многих Java приложениях, и спешим поделиться с вами этим переводом.

В этой статье мы покажем несколько малоизвестных хитростей при работе с лямбда-выражениями в Java 8 и ограничения этих выражений. Целевая аудитория статьи — senior Java разработчики, исследователи и разработчики инструментария. Будет использоваться только публичный Java API без com.sun.* и других внутренних классов, поэтому код переносим между разными реализациями JVM.

Короткое предисловие

Например, у нас есть следующий код:

Этот код будет преобразован компилятором Java во что-то похожее на:

Инструкция invokedynamic может быть примерно представлена как вот такой Java код:

В Oracle JRE 8 metafactory динамически генерирует Java класс, используя ObjectWeb Asm, который и создает класс-реализацию функционального интерфейса. К созданному классу могут быть добавлены дополнительные поля, если лямбда-выражение захватывает внешние переменные. Этот похоже на анонимные классы Java, но есть следующие отличия:

Реализация metafactory зависит от вендора JVM и от версии

Конечно же, инструкция invokedynamic используется не только для лямбда-выражений в Java. В основном, она применяется при выполнении динамических языков в среде JVM. Движок Nashorn для исполнения JavaScript, который встроен в Java, интенсивно использует эту инструкцию.

Далее мы сфокусируемся на классе LambdaMetafactory и его возможностях. Следующий
раздел этой статьи исходит из предположения, что вы отлично понимаете как работают методы metafactory и что такое MethodHandle

Трюки с лямбда-выражениями

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

Проверяемые исключения и лямбды

Не секрет, что все функциональные интерфейсы, которые есть в Java, не поддерживают проверяемые исключения. Преимущества проверяемых исключений перед обычными — это очень давний (и до сих пор горячий) спор.

А что, если вам нужно использовать код с проверяемыми исключениями внутри лямбда-выражений в сочетании с Java Streams? Например, нужно преобразовать список строк в список URL как здесь:

В конструкторе URL(String) объявлено проверяемое исключение, таким образом, он не может быть использован напрямую в виде ссылки на метод в классе Functiion.

Вы скажете: «Нет, возможно, если использовать вот такую хитрость»:

Это грязный хак. И вот почему:

Проблема может быть решена более «легальным» способом, с использованием знания следующих фактов:

Решение — обернуть метод Callable.call в метод без секции throws :

Сначала нам нужно объявить функциональный интерфейс, в котором нет секции throws
но который сможет делегировать вызов к Callable.call :

Третье — напишем вспомогательный метод, который вызывает Callable.call без объявления исключений:

Теперь можно переписать stream без всяких проблем с проверяемыми исключениями:

Этот код скомпилируется без проблем, потому что в callUnchecked нет объявления проверяемых исключений. Более того, вызов этого метода может быть заинлайнен при помощи мономорфного инлайн кэширования, потому что это только один класс во всей JVM, который реализует интерфейс SilentOnvoker

Если реализация Callable.call выкинет исключение во время выполнения, то оно будет перехвачено вызывающей функцией без всяких проблем:

Несмотря на возможности этого метода, нужно всегда помнить про следующую рекомендацию:

Скрывайте проверяемые исключения при помощи callUnchecked только если уверены, что вызываемый код не выкинет никаких исключений

Следующий пример показывает пример такого подхода:

Полная реализация этого метода находится здесь, это часть проекта с открытым кодом SNAMP.

Работаем с Getters и Setters

Этот раздел будет полезен тем, кто пишет сериализацию/десериализацию для различных форматов данных, таких как JSON, Thrift и т.д. Более того, он может быть довольно полезен, если ваш код сильно полагается на рефлексию для Getters и Setters в JavaBeans.

Первый шаг: необходимо создать кэш для getters и setters. Класс Method из Reflection API представляет реальный getter или setter и используется в качестве ключа.
Значение кэша — динамически сконструированный функциональный интерфейс для определенного getter’а или setter’а.

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

Автоматическое приведение типов между аргументами типа Object в функциональных интерфейсах (после стирания типов) и реальными типами аргументов и возвращамого значения достигается при помощи разницы между samMethodType и instantiatedMethodType (третий и пятый аргументы метода metafactory, соответственно). Тип созданного экземпляра метода — это и есть специализация метода, который предоставляет реализацию лямбда-выражения.

В-третьих, создадим фасад для этих фабрик с поддержкой кэширования:

А теперь — время тестировать код:

Этот подход с закэшированными getters и setters можно эффективно использовать в библиотеках для сериализации/десериализации (таких, как Jackson), которые используют getters и setters во время сериализации и десериализации.

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

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

Ограничения и баги

В этом разделе мы рассмотрим некоторые баги и ограничения, связанные с лямбда-выражениями в компиляторе Java и JVM. Все эти ограничения можно воспроизвести в OpenJDK и Oracle JDK с javac версии 1.8.0_131 для Windows и Linux.

Создание лямбда-выражений из обработчиков методов

Этот код эквивалентен:

Но что, если мы заменим обработчик метода, который указывает на getValue на обработчик, который представляет getter поля:

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

Что интересно, getter для поля работает нормально, если будем использовать MethodHandleProxies:

Нужно отметить, что MethodHandleProxies — не очень хороший способ для динамического создания лямбда-выражений, потому что этот класс просто оборачивает MethodHandle в прокси-класс и делегирует вызов InvocationHandler.invoke методу MethodHandle.invokeWithArguments. Этот подход использует Java Reflection и работает очень медленно.

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

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

Generic исключения

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

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

Ограничения типов параметризации

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

Этот код абсолютно корректный и успешно компилируется. Класс MutableInteger удовлетворяет ограничениям обобщенного типа T:

Но код упадет с исключением во время выполнения:

Этот пример демонстрирует некорректный вывод типов в компиляторе и среде исполнения.

Обработка нескольких ограничений типов generic параметров в сочетании с использованием лямбда-выражений во время компиляции и выполнения — неконсистентна

Источник

Новое в Java 8

Методы интерфейсов по умолчанию

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

Давайте начнем с простого примера: сортировка массива строк в предыдущих версиях языка.

Статический метод Collections.sort принимает список и компаратор, который используется для сортировки списка. Наверняка вам часто приходилось создавать анонимные компараторы для того чтобы передать их в метод.

Java 8 предоставляет гораздо более короткий синтаксис — лямбда-выражения, чтобы вам не приходилось тратить время на создание анонимных объектов:

Как видите, код гораздо короче и куда более читаем. И его можно сделать еще короче:

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

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

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

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

Предыдущий пример можно упростить, если использовать статические ссылки на методы:

Давайте посмотрим, как передавать ссылки на конструкторы. Сперва определим бин с несколькими конструкторами:

Затем определим интерфейс фабрики, которая будет использоваться для создания новых персон:

Теперь вместо реализации интерфейса мы соединяем все вместе при помощи ссылки на конструктор:

Области действия лямбд

Однако переменная num должна все равно оставаться неизменяемой. Следующий код не скомпилируется:

Запись в переменную num в пределах лямбда-выражения также запрещена.

Доступ к полям и статическим переменным

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

Доступ к методам интерфейсов по умолчанию

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

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

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

Предикаты
Функции
Поставщики

Поставщики (suppliers) предоставляют результат заданного типа. В отличии от функций, поставщики не принимают аргументов.

Потребители

Потребители (consumers) представляют собой операции, которые производятся на одним входным аргументом.

Компараторы

Компараторы хорошо известны по предыдущим версиям Java. Java 8 добавляет в интерфейс различные методы по умолчанию.

Опциональные значения

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

Опциональные значение — это по сути контейнер для значения, которое может быть равно null. Например, вам нужен метод, который возвращает какое-то значение, но иногда он должен возвращать пустое значение. Вместо того, чтобы возвращать null, в Java 8 вы можете вернуть опциональное значение.

Потоки

Сначала давайте посмотрим, как работать с потоком последовательно. Сперва создадим источник в виде списка строк:

Filter

Операция Filter принимает предикат, который фильтрует все элементы потока. Эта операция является промежуточной, т.е. позволяет нам вызвать другую операцию (например, forEach ) над результатом. ForEach принимает функцию, которая вызывается для каждого элемента в (уже отфильтрованном) поток. ForEach является конечной операцией. Она не возращает никакого значения, поэтому дальнейший вызов потоковых операций невозможен.

Sorted

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

Помните, что sorted создает всего лишь отсортированное представление и не влияет на порядок элементов в исходной коллекции. Порядок строк в stringCollection остается нетронутым:

Match

Для проверки, удовлетворяет ли поток заданному предикату, используются различные операции сопоставления (match). Все операции сопоставления являются конечными и возвращают результат типа boolean.

Count
Reduce

Эта конечная операция производит свертку элементов потока по заданной функции. Результатом является опциональное значение.

Параллельные потоки

Как уже упоминалось выше, потоки могут быть последовательными и параллельными. Операции над последовательными потоками выполняются в одном потоке процессора, над параллельными — используя несколько потоков процессора.

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

Сперва создадим большой список из уникальных элементов:

Теперь измерим время сортировки этого списка.

Последовательная сортировка
Параллельная сортировка

Ассоциативные массивы

Как уже упоминалось, ассоциативные массивы (maps) не поддерживают потоки. Вместо этого ассоциативные массивы теперь поддерживают различные полезные методы, которые решают часто встречаемые задачи.

Этот код в особых комментариях не нуждается: putIfAbsent позволяет нам не писать дополнительные проверки на null; forEach принимает потребителя, который производит операцию над каждым элементом массива.

Этот код показывает как использовать для вычислений код при помощи различных функций:

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

Еще один полезный метод:

Объединить записи двух массивов? Легко:

В случае отсутствия ключа Merge создает новую пару ключ-значение. В противном случае — вызывает функцию объединения для существующего значения.

API для работы с датами

Clock
Часовые пояса
LocalTime

Тип LocalTime представляет собой время с учетом часового пояса, например, 10pm или 17:30:15. В следующем примере создаются два местных времени для часовых поясов, определенных выше. Затем оба времени сравниваются, и вычисляется разница между ними в часах и минутах.

Тип LocalTime содержит различные фабричные методы, которые упрощают создание новых экземпляров, а также парсинг строк.

LocalDate

Создание экземпляра LocalDate путем парсинга строки:

LocalDateTime

Форматирование даты-времени работает так же, как и форматирование даты или времени. Мы можем использовать библиотечные или свои собственные шаблоны.

Подробно о синтаксисе шаблонов можно почитать здесь.

Аннотации

Аннотации в Java 8 являются повторяемыми. Давайте сразу посмотрим пример, чтобы понять, что это такое.

Сперва мы определим аннотацию-обертку, которая содержит массив аннотаций:

Вариант 1: использовать аннотацию-контейнер (старый способ)

Вариант 2: использовать повторяемую аннотацию (новый способ)

Более того, аннотации в Java 8 можно использовать еще на двух элементах:

Вот и все

Полный исходный код статьи доступен на GitHub.

Источник

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. Решение квадратного уравнения с помощью лямбда-выражения

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

Источник

Добавить комментарий

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