Что такое лямбда выражение в java
- мама мыла раму
- мир труд май
- я очень люблю java
- мир труд май
- мама мыла раму
- я очень люблю java
Поговорим об интерфейсах
В принципе, интерфейс — это просто список абстрактных методов. Когда мы создаем класс и говорим, что он будет имплементировать какой-то интерфейс — мы должны в нашем классе написать реализацию тех методов, которые перечислены в интерфейсе (или, на крайний случай, не писать, но сделать класс абстрактным). Бывают интерфейсы со множеством разных методов (например List ), бывают интерфейсы только с одним методом (например, тот же Comparator или Runnable). Бывают интерфейсы и вовсе без единого метода (так называемые интерфейсы-маркеры, например Serializable). Те интерфейсы, у которых только один метод, также называют функциональными интерфейсами. В Java 8 они даже помечены специальной аннотацией @FunctionalInterface. Именно интерфейсы с одним единственным методом и подходят для использования лямбда-выражениями. Как я уже говорил выше, лямбда-выражение — это метод, завернутый в объект. И когда мы передаем куда-то такой объект — мы, по сути, передаем этот один единственный метод. Получается, нам не важно, как этот метод называется. Все, что нам важно — это параметры, которые этот метод принимает, и, собственно, сам код метода. Лямбда-выражение — это, по сути. реализация функционального интерфейса. Где видим интерфейс с одним методом — значит, такой анонимный класс можем переписать через лямбду. Если в интерфейсе больше/меньше одного метода — тогда нам лямбда-выражение не подойдет, и будем использовать анонимный класс, или даже обычный. Пришло время поковырять лямбды. :)
Синтаксис
Примеры
Пример 1. Самый простой вариант. И самый бессмысленный:).Так как ничего не делает. Пример 2. Тоже интересный вариант. Ничего не принимает и возвращает пустую строку ( return опущен за ненадобностью). То же, но с return : Пример 3. Hello world на лямбдах Ничего не принимает, ничего не возвращает (мы не можем поставить return перед вызовом System.out.println() , так как тип возвращаемого значения в методе println() — void) , просто выводит на экран надпись. Идеально подходит для реализации интерфейса Runnable . Этот же пример более полный: Ну, или так: Или даже можем сохранить лямбда-выражение как объект типа Runnable , а потом его уже передать в конструктор thread’а : Рассмотрим подробнее момент сохранения лямбда-выражения в переменную. Интерфейс Runnable нам говорит, что его объекты должны иметь метод public void run() . Согласно интерфейсу, метод run ничего не принимает в качестве параметров. И ничего не возвращает (void) . Поэтому при такой записи будет создан объект с каким-то методом, который ничего не принимает и не возвращает. Что вполне соответствует методу run() в интерфейсе Runnable . Вот почему мы и смогли поместить это лямбда-выражение в переменную типа Runnable . Пример 4 Снова, ничего не принимает, а возвращает число 42. Такое лямбда-выражение можно поместить в переменную типа Callable , потому что в этом интерфейсе определен только один метод, который выглядит примерно так: где V — это тип возвращаемого значения (в нашем случае int ). Соответственно, мы можем сохранить такое лямбда-выражение следующим образом: Пример 5. Лямбда в несколько строк Опять, это лямбда-выражение без параметров и тип возвращаемого значения у него void (так как отсутствует return ). Пример 6 Тут мы принимаем что-то в переменную х , и ее же и возвращаем. Обратите внимание, что если принимается только один параметр — то скобки вокруг него можно не писать. То же, но со скобками: А вот вариант с явным return : Или так, со скобками и return : Или с явным указанием типа (и, соответственно, со скобками): Пример 7 Принимаем х , возвращаем его же, но на 1 больше. Можно переписать и так: В обоих случаях скобки вокруг параметра, тела метода и слово return не указываем, так как это не обязательно. Варианты со скобками и с ретурном описаны в примере 6. Пример 8 Принимаем какие-то х и у , возвращаем остаток от деления x на y . Скобки вокруг параметров тут уже обязательны. Необязательны они только когда параметр всего один. Вот так с явным указанием типов: Пример 9 Принимаем объект Кот, строку с именем и целое число возраст. В самом методе устанавливаем Коту переданные имя и возраст. Поскольку переменная cat у нас ссылочного типа, то и объект Кот вне лямбда-выражения изменится (получит переданные внутрь имя и возраст). Немного усложненный вариант, где используется подобная лямбда: Автор во всю использует дженерики(параметры-типы), которые в основном квесте рассматривались очень поверхностно и без использования в интерфейсах. Советую изучить данные примеры очень подробно.
От переводчика: LambdaMetafactory, пожалуй, один из самых недооценённых механизмов Java 8. Мы открыли его для себя совсем недавно, но уже по достоинству оценили его возможности. В версии 7.0 фреймворка CUBA улучшена производительность за счет отказа от рефлективных вызовов в пользу генерации лямбда выражений. Одно из применений этого механизма в нашем фреймворке — привязка обработчиков событий приложения по аннотациям, часто встречающаяся задача, аналог EventListener из Spring. Мы считаем, что знание принципов работы LambdaFactory может быть полезно во многих Java приложениях, и спешим поделиться с вами этим переводом.
В этой статье мы покажем несколько малоизвестных хитростей при работе с лямбда-выражениями в Java 8 и ограничения этих выражений. Целевая аудитория статьи — senior Java разработчики, исследователи и разработчики инструментария. Будет использоваться только публичный Java API без com.sun.* и других внутренних классов, поэтому код переносим между разными реализациями JVM.
Короткое предисловие
Лямбда-выражения появились в Java 8 как способ имплементации анонимных методов и,
в некоторых случаях, как альтернатива анонимным классам. На уровне байткода лямбда-выражение заменяется инструкцией invokedynamic . Эта инструкция используется для создания реализации функционального интерфейса и его единственный метод делегирует вызов фактическому методу, который содержит код, определенный в теле лямбда-выражения.
Например, у нас есть следующий код:
Этот код будет преобразован компилятором Java во что-то похожее на:
Инструкция invokedynamic может быть примерно представлена как вот такой Java код:
Как видно, LambdaMetafactory применяется для создания CallSite который предоставляет фабричный метод, возвращающий обработчик целевого метода,. Этот метод возвращает реализацию функционального интерфейса, используя invokeExact . Если в лямбда-выражении есть захваченные переменные, то invokeExact принимает эти переменные как фактические параметры.
В Oracle JRE 8 metafactory динамически генерирует Java класс, используя ObjectWeb Asm, который и создает класс-реализацию функционального интерфейса. К созданному классу могут быть добавлены дополнительные поля, если лямбда-выражение захватывает внешние переменные. Этот похоже на анонимные классы Java, но есть следующие отличия:
- Анонимный класс генерируется компилятором Java.
- Класс для реализации лямбда-выражения создается JVM во время выполнения.
Реализация metafactory зависит от вендора JVM и от версии
Конечно же, инструкция invokedynamic используется не только для лямбда-выражений в Java. В основном, она применяется при выполнении динамических языков в среде JVM. Движок Nashorn для исполнения JavaScript, который встроен в Java, интенсивно использует эту инструкцию.
Далее мы сфокусируемся на классе LambdaMetafactory и его возможностях. Следующий
раздел этой статьи исходит из предположения, что вы отлично понимаете как работают методы metafactory и что такое MethodHandle
Трюки с лямбда-выражениями
В этом разделе мы покажем, как строить динамические конструкции из лямбд для использования в ежедневных задачах.
Проверяемые исключения и лямбды
Не секрет, что все функциональные интерфейсы, которые есть в Java, не поддерживают проверяемые исключения. Преимущества проверяемых исключений перед обычными — это очень давний (и до сих пор горячий) спор.
А что, если вам нужно использовать код с проверяемыми исключениями внутри лямбда-выражений в сочетании с Java Streams? Например, нужно преобразовать список строк в список URL как здесь:
В конструкторе URL(String) объявлено проверяемое исключение, таким образом, он не может быть использован напрямую в виде ссылки на метод в классе Functiion.
Вы скажете: "Нет, возможно, если использовать вот такую хитрость":
Это грязный хак. И вот почему:
- Используется блок try-catch.
- Исключение выбрасывается ещё раз.
- Грязное использование стирания типов в Java.
Проблема может быть решена более "легальным" способом, с использованием знания следующих фактов:
- Проверяемые исключения распознаются только на уровне Java компилятора.
- Секция throws — это всего лишь метаданные для метода без семантического значения на уровне JVM.
- Проверяемые и обычные исключения неразличимы на уровне байткода в JVM.
Решение — обернуть метод Callable.call в метод без секции throws :
Этот код не скомпилируется, потому что у метода Callable.call объявлены проверяемые исключения в секции throws . Но мы можем убрать эту секцию, используя динамически сконструированное лямбда-выражение.
Сначала нам нужно объявить функциональный интерфейс, в котором нет секции throws
но который сможет делегировать вызов к Callable.call :
Второй шаг — создать реализацию этого интерфейса, используя LambdaMetafactory и делегировать вызов метода SilentInvoker.invoke методу Callable.call . Как было сказано ранее, секция throws игнорируется на уровне байткода, таким образом, метод SilentInvoker.invoke сможет вызвать метод Callable.call без объявления исключений:
Третье — напишем вспомогательный метод, который вызывает Callable.call без объявления исключений:
Теперь можно переписать stream без всяких проблем с проверяемыми исключениями:
Этот код скомпилируется без проблем, потому что в callUnchecked нет объявления проверяемых исключений. Более того, вызов этого метода может быть заинлайнен при помощи мономорфного инлайн кэширования, потому что это только один класс во всей JVM, который реализует интерфейс SilentOnvoker
Если реализация Callable.call выкинет исключение во время выполнения, то оно будет перехвачено вызывающей функцией без всяких проблем:
Несмотря на возможности этого метода, нужно всегда помнить про следующую рекомендацию:
Скрывайте проверяемые исключения при помощи callUnchecked только если уверены, что вызываемый код не выкинет никаких исключений
Следующий пример показывает пример такого подхода:
Полная реализация этого метода находится здесь, это часть проекта с открытым кодом SNAMP.
Работаем с Getters и Setters
Этот раздел будет полезен тем, кто пишет сериализацию/десериализацию для различных форматов данных, таких как JSON, Thrift и т.д. Более того, он может быть довольно полезен, если ваш код сильно полагается на рефлексию для Getters и Setters в JavaBeans.
Getter, объявленный в JavaBean — это метод с именем getXXX без параметров и возвращаемым типом данных, отличным от void . Setter, объявленный в JavaBean — метод с именем setXXX , с одним параметром и возвращающий void . Эти две нотации могут быть представленв как функциональные интерфейсы:
- Getter может быть представлен классом Function, в котором аргумент — значение this .
- Setter может быть представлен классом BiConsumer, в котором первый аргумент — this , а второй — значение, которое передается в Setter.
Теперь мы создадим два метода, которые смогут преобразовать любой getter или setter в эти
функциональные интерфейсы. И неважно, что оба интерфейса — generics. После стирания типов
реальный тип данных будет Object . Автоматическое приведение возвращаемого типа и аргументов может быть сделано при помощи LambdaMetafactory . В дополнение, библиотека Guava поможет с кэшированием лямбда-выражений для одинаковых getters и setters.
Первый шаг: необходимо создать кэш для getters и setters. Класс Method из Reflection API представляет реальный getter или setter и используется в качестве ключа.
Значение кэша — динамически сконструированный функциональный интерфейс для определенного getter'а или setter'а.
Во-вторых, создадим фабричные методы, которые создают экземпляр функционального интерфейса на основе ссылок на getter или setter.
Автоматическое приведение типов между аргументами типа Object в функциональных интерфейсах (после стирания типов) и реальными типами аргументов и возвращамого значения достигается при помощи разницы между samMethodType и instantiatedMethodType (третий и пятый аргументы метода metafactory, соответственно). Тип созданного экземпляра метода — это и есть специализация метода, который предоставляет реализацию лямбда-выражения.
В-третьих, создадим фасад для этих фабрик с поддержкой кэширования:
Информация о методе, полученная из экземпляра класса Method с использованием Java Reflection API может быть легко преобразована в MethodHandle . Примите во внимание, что у методов экземпляров класса, всегда есть скрытый первый аргумент, используемый для передачи this в этот метод. У статических методов такого параметра нет. Например, реальная сигнатура метода Integer.intValue() выглядит как int intValue(Integer this) . Эта хитрость используется в нашей имплементации функциональных оберток для getters и setters.
А теперь — время тестировать код:
Этот подход с закэшированными getters и setters можно эффективно использовать в библиотеках для сериализации/десериализации (таких, как Jackson), которые используют getters и setters во время сериализации и десериализации.
Вызовы функциональных интерфейсов с динамически сгенерированными реализациями с использованием LambdaMetaFactory значительно быстрее, чем вызовы через Java Reflection API
Полную версию кода можно найти здесь, это часть библиотеки SNAMP.
Ограничения и баги
В этом разделе мы рассмотрим некоторые баги и ограничения, связанные с лямбда-выражениями в компиляторе Java и JVM. Все эти ограничения можно воспроизвести в OpenJDK и Oracle JDK с javac версии 1.8.0_131 для Windows и Linux.
Создание лямбда-выражений из обработчиков методов
Как вы знаете, лямбда-выражение можно сконструировать динамически, используя LambdaMetaFactory . Чтобы это сделать, нужно определить обработчик — класс MethodHandle , который указывает на реализацию единственного метода, который определен в функциональном интерфейсе. Давайте взглянем на этот простой пример:
Этот код эквивалентен:
Но что, если мы заменим обработчик метода, который указывает на getValue на обработчик, который представляет getter поля:
Этот код должен, ожидаемо, работать, потому что findGetter возвращает обработчик, который указывает на getter поля и у него правильная сигнатура. Но, если вы запустите этот код, то увидите следующее исключение:
Что интересно, getter для поля работает нормально, если будем использовать MethodHandleProxies:
Нужно отметить, что MethodHandleProxies — не очень хороший способ для динамического создания лямбда-выражений, потому что этот класс просто оборачивает MethodHandle в прокси-класс и делегирует вызов InvocationHandler.invoke методу MethodHandle.invokeWithArguments. Этот подход использует Java Reflection и работает очень медленно.
Как было показано ранее, не все обработчики методов могут быть использованы для создания лямбда-выражений во время выполнения кода.
Только несколько типов обработчиков методов могут быть использованы для динамического создания лямбда-выражений
- REF_invokeInterface: может быть создан при помощи Lookup.findVirtual для методов интерфейсов
- REF_invokeVirtual: может быть создан с помощью Lookup.findVirtual для виртуальных методов класса
- REF_invokeStatic: создается при помощи Lookup.findStatic для статических методов
- REF_newInvokeSpecial: может быть создан при помощи Lookup.findConstructor для конструкторов
- REF_invokeSpecial: может быть создан с помощью Lookup.findSpecial
для приватных методов и раннего связывания с виртуальными методами класса
Остальные типы обработчиков вызовут ошибку LambdaConversionException .
Generic исключения
Этот баг связан с компилятором Java и возможностью объявлять generic исключения в секции throws . Следующий пример кода демонстрирует это поведение:
Но, если мы заменим лямбда-выражение анонимным классом, то код скомпилируется:
Из этого следует:
Вывод типов для generic исключений не работет корректно в сочетании с лямбда-выражениями
Ограничения типов параметризации
Можно сконструировать generic объект с несколькими ограничениями типов, используя знак & : .
Такой способ определения generic параметров редко используется, но определенным образом влияет на лямбда-выражения в Java из-за некоторых ограничений:
- Каждое ограничение типа, кроме первого, должно быть интерфейсом.
- Чистая версия класса с таким generic учитывает только первое ограничение типа из списка.
Второе ограничение приводит к разному поведению кода во время компиляции и во время выполнения, когда происходит связываение с лямбда-выражения. Эту разницу можно продемонстрировать, используя следующий код:
Этот код абсолютно корректный и успешно компилируется. Класс MutableInteger удовлетворяет ограничениям обобщенного типа T:
- MutableInteger наследуется от Number .
- MutableInteger реализует IntSupplier .
Но код упадет с исключением во время выполнения:
Этот пример демонстрирует некорректный вывод типов в компиляторе и среде исполнения.
Обработка нескольких ограничений типов generic параметров в сочетании с использованием лямбда-выражений во время компиляции и выполнения — неконсистентна
Java изначально полностью объектно-ориентированный язык. За исключением примитивных типов, все в Java – это объекты. Даже массивы являются объектами. Экземпляры каждого класса – объекты. Не существует ни единой возможности определить отдельно (вне класса – прим. перев.) какую-нибудь функцию. И нет никакой возможности передать метод как аргумент или вернуть тело метода как результат другого метода. Все так. Но так было до Java 8. Со времен старого доброго Swing, надо было писать анонимные классы, когда нужно было передать некую функциональность в какой-нибудь метод. Например, так выглядело добавление обработчика событий: Здесь мы хотим добавить некоторый код в слушатель событий от мыши. Мы определили анонимный класс MouseAdapter и сразу создали объект из него. Таким способом мы передали дополнительную функциональность в метод addMouseListener . Короче говоря, не так-то просто передать простой метод (функциональность) в Java через аргументы. Это ограничение вынудило разработчиков Java 8 добавить в спецификацию языка такую возможность как Lambda-выражения.
Зачем яве Lambda-выражения?
С самого начала, язык Java особо не развивался, если не считать такие вещи как аннотации (Annotations), дженерики (Generics) и пр. В первую очередь, Java всегда оставался объектно-ориентированным. После работы с функциональными языками, такими как JavaScript, можно понять насколько Java строго объектно-ориентирован и строго типизирован. Функции в Java не нужны. Сами по себе их нельзя встретить в мире Java. В функциональных языках программирования на первый план выходят функции. Они существуют сами по себе. Можно присваивать их переменным и передавать через аргументы другим функциям. JavaScript один из лучших примеров функциональных языков программирования. На просторах Интернета можно найти хорошие статьи, в которых детально описаны преимущества JavaScript как функционального языка. Функциональные языки имеют в своем арсенале такие мощные инструменты как замыкания (Closure), которые обеспечивают ряд преимуществ на традиционными способами написания приложений. Замыкание – это функция с привязанной к ней средой — таблицей, хранящей ссылки на все нелокальные переменные функции. В Java замыкания можно имитировать через Lambda-выражения. Безусловно между замыканиями и Lambda-выражениями есть отличия и не малые, но лямбда выражения являются хорошей альтернативой замыканиям. В своем саркастичном и забавном блоге, Стив Иег (Steve Yegge) описывает насколько мир Java строго завязан на имена существительные (сущности, объекты – прим. перев.). Если вы не читали его блог, рекомендую. Он забавно и интересно описывает точную причину того, почему в Java добавили Lambda-выражения. Lambda-выражения привносят в Java функциональное звено, которого так давно не хватало. Lambda-выражения вносят в язык функциональность на равне с объектами. Хотя это и не на 100% верно, можно видеть, что Lambda-выражения не являясь замыканиями предоставляют схожие возможности. В функциональном языке lambda-выражения – это функции; но в Java, lambda-выражения – представляются объектами, и должны быть связаны с конкретным объектным типом, который называется функциональный интерфейс. Далее мы рассмотри, что он из себя представляет. В статье Марио Фаско (Mario Fusco) “Зачем в Java нужны Lambda-выражения” (“Why we need Lambda Expression in Java”) подробно описано, зачем всем современным языкам нужны возможности замыканий.
Введение в Lambda-выражения
Lambda-выражения – это анонимные функции (может и не 100% верное определение для Java, но зато привносит некоторую ясность). Проще говоря, это метод без объявления, т.е. без модификаторов доступа, возвращающие значение и имя. Короче говоря, они позволяют написать метод и сразу же использовать его. Особенно полезно в случае однократного вызова метода, т.к. сокращает время на объявление и написание метода без необходимости создавать класс. Lambda-выражения в Java обычно имеют следующий синтаксис (аргументы) -> (тело) . Например: Далее идет несколько примеров настоящих Lambda-выражений:
Структура Lambda-выражений
- Lambda-выражения могут иметь от 0 и более входных параметров.
- Тип параметров можно указывать явно либо может быть получен из контекста. Например ( int a ) можно записать и так ( a )
- Параметры заключаются в круглые скобки и разделяются запятыми. Например ( a, b ) или ( int a, int b ) или ( String a , int b , float c )
- Если параметров нет, то нужно использовать пустые круглые скобки. Например () -> 42
- Когда параметр один, если тип не указывается явно, скобки можно опустить. Пример: a -> return a*a
- Тело Lambda-выражения может содержать от 0 и более выражений.
- Если тело состоит из одного оператора, его можно не заключать в фигурные скобки, а возвращаемое значение можно указывать без ключевого слова return .
- В противном случае фигурные скобки обязательны (блок кода), а в конце надо указывать возвращаемое значение с использованием ключевого слова return (в противном случае типом возвращаемого значения будет void ).
Что такое функциональный интерфейс
Примеры Lambda-выражений
Лучший способ вникнуть в Lambda-выражения – это рассмотреть несколько примеров: Поток Thread можно проинициализировать двумя способами: Управление событиями в Java 8 также можно осуществлять через Lambda-выражения. Далее представлены два способа добавления обработчика события ActionListener в компонент пользовательского интерфейса: Простой пример вывода всех элементов заданного массива. Заметьте, что есть более одного способа использования lambda-выражения. Ниже мы создаем lambda-выражение обычным способом, используя синтаксис стрелки, а также мы используем оператор двойного двоеточия (::) , который в Java 8 конвертирует обычный метод в lambda-выражение: В следующем примере мы используем функциональный интерфейс Predicate для создания теста и печати элементов, прошедших этот тест. Таким способом вы можете помещать логику в lambda-выражения и делать что-либо на ее основе. Вывод: Поколдовав над Lambda-выражениями можно вывести квадрат каждого элемента списка. Заметьте, что мы используем метод stream() , чтобы преобразовать обычный список в поток. Java 8 предоставляет шикарный класс Stream ( java.util.stream.Stream ). Он содержит тонны полезных методов, с которыми можно использовать lambda-выражения. Мы передаем lambda-выражение x -> x*x в метод map() , который применяет его ко всем элементам в потоке. После чего мы используем forEach для печати всех элементов списка. Дан список, нужно вывести сумму квадратов всех элемента списка. Lambda-выражения позволяет достигнуть этого написанием всего одной строки кода. В этом примере применен метод свертки (редукции) reduce() . Мы используем метод map() для возведения в квадрат каждого элемента, а потом применяем метод reduce() для свертки всех элементов в одно число.
Отличие Lambda-выражений от анонимных класов
Главное отличие состоит в использовании ключевого слова this . Для анонимных классов ключевое слово ‘ this ’ обозначает объект анонимного класса, в то время как в lambda-выражении ‘ this ’ обозначает объект класса, в котором lambda-выражение используется. Другое их отличие заключается в способе компиляции. Java компилирует lambda-выражения с преобразованием их в private -методы класса. При этом используется инструкция invokedynamic, появившаяся в Java 7 для динамической привязки метода. Тал Вайс (Tal Weiss) описал в своем блоге как Java компилирует lambda-выражения в байт-код
Заключение
Марк Рейнхолд (Mark Reinhold - Oracle’s Chief Architect), назвал Lambda-выражения самым значительным изменением в модели программирования, которое когда-либо происходило — даже более значительным, чем дженерики (generics). Должно быть он прав, т.к. они дают Java программистам возможности функциональных языков программирования, которых так давно все ждали. Наряду с такими новшествами как методы виртуального расширения (Virtual extension methods), Lambda-выражения позволяют писать очень качественный код. Я надеюсь, что это статья позволила вам взглянуть под капот Java 8. Удачи :)
Среди новшеств, которые были привнесены в язык 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 :
Обобщенный функциональный интерфейс
Функциональный интерфейс может быть обобщенным, однако в лямбда-выражении использование обобщений не допускается. В этом случае нам надо типизировать объект интерфейса определенным типом, который потом будет применяться в лямбда-выражении. Например:
Таким образом, при объявлении лямбда-выражения ему уже известно, какой тип параметры будут представлять и какой тип они будут возвращать.
Читайте также: