Что такое lambda выражение
Объяснение лямбда-выражений
У меня возникли вопросы о лямбда-выражениях и RxJava. Эти вопросы в основном касаются не полного понимания лямбда-выражений или RxJava. Я попытаюсь объяснить лямбда-выражения как можно проще. RxJava я опишу отдельно.
Лямбда-выражения и RxJava
Что такое лямбда-выражения? Лямбда-выражения – это «всего лишь» новый способ сделать то же самое, что мы всегда могли сделать, но в более чистом и менее многословном новом способе использования анонимных внутренних классов.
Анонимный внутренний класс в Java – это класс без имени, он должен использоваться, если вам необходимо переопределить методы класса или интерфейса. Анонимный внутренний класс может быть создан из класса или интерфейса.
В Android мы обычно используем анонимный внутренний класс в качестве слушателя, например, для кнопок такого рода:
Вернемся к лямбда-выражениям. Это следующая часть, которая является частью предыдущего кода, который считается анонимным внутренним классом.
Лямбда-выражения могут использоваться только в том случае, если вам нужно переопределить не более одного метода. К счастью для нас, View.OnClickListener содержит только один. Посмотрите на код ниже. Как думаете, какую его часть нам придётся убрать?
В некоторых случаях вам может потребоваться добавить тип к параметру, если компилятору не удается его угадать, вы можете сделать это, добавив его перед параметром:
Вы также можете использовать многострочный код:
Если у вас есть интерфейс с методом, принимающим два параметра…
…лямбда-выражение будет выглядеть следующим образом:
Если метод имеет возвращаемый тип…
…лямбда-выражение будет выглядеть следующим образом:
Но это еще не все, есть некоторые специальные случаи, которые делают код еще меньше. Если тело вашего метода содержит только одну строку кода, вы можете удалить фигурные скобки <>. Если вы удаляете фигурные скобки, вам также необходимо удалить точку с запятой, оставив следующее:
Есть еще одна вещь, которую мы можем сделать. Если у нас только одна строка кода, то компилятор может понять, нужна ли возвращаемая часть или нет, поэтому мы можем оставить ее следующим образом:
Если бы у нас был многострочный код, это свелось бы к следующему:
Так что же мы сделали? Мы взяли это:
и превратили вот в это:
Осталось только одна вещь, ссылки на методы. Допустим, у нас есть интерфейс, как и раньше, и метод, который этот интерфейс принимает как параметр:
Без лямбда-выражения это выглядело бы так:
Добавив лямбда-выражение, как мы это делали раньше, получим следующее:
Но это еще не все. Если используемый код – однострочный и вызываемая функция принимает один параметр, мы можем передавать ссылку на метод в таком виде:
Параметр будет передаваться автоматически, без необходимости использования другого кода! Удивительно, правда? Надеюсь, вы узнали что-то новое!
Все, что нужно знать о lambda-функциях в Python
В этой статье вы узнаете о том, что такое лямбда-функции в Python. На самом деле, если вы знаете, что такое функции и умеете с ними работать, то знаете и что такое лямбда.
Что такое лямбда в Python?
Прежде чем переходить как разбору понятия лямбда в Python, попробуем понять, чем является обычная функция Python на более глубоком уровне.
Для этого потребуется немного поменять направление мышление. Как вы знаете, все в Python является объектом.
Например, когда мы запускаем эту простейшую строку кода
А что происходит при определении вот такой функции:
Это же значит, что функции можно передать в качестве аргументов другим функциям или даже использовать их как тип возвращаемого значения.
Рассмотрим простой пример, где функция f передается другой функции.
Попробуйте разобраться самостоятельно с тем, что делает этот код, прежде чем читать дальше.
Итак, modify_list — это функция, которая принимает список L и функцию fn в качестве аргументов. Затем она перебирает список элемент за элементом и применяет функцию к каждому из них.
Но можно передать и любую другую, которая изменит оригинальный список другим способом. Это очень мощный инструмент.
Теперь, когда с основами разобрались, стоит перейти к лямбда. Лямбда в Python — это просто еще один способ определения функции. Вот базовый синтаксис лямбда-функции в Python:
Лямбда принимает любое количество аргументов (или ни одного), но состоит из одного выражения. Возвращаемое значение — значение, которому присвоена функция. Например, если нужно определить функцию f из примера выше, то это можно сделать вот так:
Но возникает вопрос: а зачем нужны лямбда-функции, если их можно объявлять традиционным образом? Но на самом деле, они полезны лишь в том случае, когда нужна одноразовая функция. Такие функции еще называют анонимными. И, как вы увидите дальше, есть масса ситуаций, где они оказываются нужны.
Лямбда с несколькими аргументами
Определить лямбда-функцию с одним аргументом не составляет труда.
А если их должно быть несколько, то достаточно лишь разделить значения запятыми. Предположим, что нужна функция, которая берет два числовых аргумента и возвращает их произведение.
Отлично! А как насчет лямбда-функции без аргументов?
Лямбда-функция без аргументов
Несколько лямбда-функций
В определенный момент возникнет вопрос: а можно ли иметь лямбда-функцию из нескольких строк.
Ответ однозначен: нет.
Лямбда-функции в Python всегда принимают только одно выражение. Если же их несколько, то лучше создать обычную функцию.
Примеры лямбда-функций
Теперь рассмотрим самые распространенные примеры использования лямбда-функций.
Лямбда-функция и map
Распространенная операция со списками в Python — применение операции к каждому элементу.
map() — это встроенная функция Python, принимающая в качестве аргумента функцию и последовательность. Она работает так, что применяет переданную функцию к каждому элементу.
Так, вместо определения функции и передачи ее в map в качестве аргумента, можно просто использовать лямбда для быстрого определения ее прямо внутри. В этом есть смысл, если упомянутая функция больше не будет использоваться в коде.
Вот еще один пример.
Лямбда-функция и filter
filter() — это еще одна встроенная функция, которая фильтрует последовательность итерируемого объекта.
Другими словами, функция filter отфильтровывает некоторые элементы итерируемого объекта (например, списка) на основе какого-то критерия. Критерий определяется за счет передачи функции в качестве аргумента. Она же применяется к каждому элементу объекта.
С лямбда-функциями это все можно сделать максимально сжато. Код выше можно преобразовать в такой, написанный в одну строку.
И в этом сила лямбда-функций.
Лямбда-функция и сортировка списков
Теперь создадим экземпляры этого класса и добавим их в список.
Предположим, что мы хотим отсортировать его на основе поля age сотрудников. Вот что нужно сделать для этого:
Пара слов о выражениях и инструкциях
Как уже упоминалось, лямбда могут иметь только одно выражение (expression) в теле.
Обратите внимание, что речь идет не об инструкции (statement).
Выражение и инструкции — две разные вещи, в которых часто путаются. В программировании инструкцией является строка кода, выполняющая что-то, но не генерирующая значение.
Например, инструкция if или циклы for и while являются примерами инструкций. Заменить инструкцию на значение попросту невозможно.
А вот выражения — это значения. Запросто можно заменить все выражения в программе на значения, и программа продолжит работать корректно.
Тело лямбда-функции должно являться выражением, поскольку его значение будет тем, что она вернет. Обязательно запомните это для работы с лямбда-функциями в будущем.
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 8
В новой версии Java 8 наконец-то появились долгожданные лямбда-выражения. Возможно, это самая важная новая возможность последней версии; они позволяют писать быстрее и делают код более ясным, а также открывают дверь в мир функционального программирования. В этой статье я расскажу, как это работает.
Java задумывалась как объектно-ориентированный язык в 90-е годы, когда объектно-ориентированное программирование было главной парадигмой в разработке приложений. Задолго до этого было объектно-ориентированное программирование, были функциональные языки программирования, такие, как Lisp и Scheme, но их преимущества не были оценены за пределами академической среды. В последнее время функциональное программирование сильно выросло в значимости, потому что оно хорошо подходит для параллельного программирования и программирования, основанного на событиях («reactive»). Это не значит, что объектная ориентированность – плохо. Наоборот, вместо этого, выигрышная стратегия – смешивать объектно-ориентированное программирование и функциональное. Это имеет смысл, даже если вам не нужна параллельность. Например, библиотеки коллекций могут получить мощное API, если язык имеет удобный синтаксис для функциональных выражений.
Главным улучшением в Java 8 является добавление поддержки функциональных программных конструкций к его объектно-ориентированной основе. В этой статье я продемонстрирую основной синтаксис и как использовать его в нескольких важных контекстах. Ключевые моменты понятия лямбды:
Зачем нужны лямбды?
Лямбда-выражение представляет собой блок кода, который можно передать в другое место, поэтому он может быть выполнен позже, один или несколько раз. Прежде чем углубляться в синтаксис (и любопытное название), давайте сделаем шаг назад и увидим, где вы использовали аналогичные блоки кода в Java до этого.
Ключевым моментом является то, что метод run содержит код, который нужно выполнить в отдельном потоке.
Рассмотрим сортировку с использованием пользовательского компаратора. Если вы хотите отсортировать строки по длине, а не по умолчанию, вы можете передать объект Comparator в метод sort :
В качестве другого примера отложенного выполнения рассмотрим коллбэк для кнопки. Вы помещаете действие обратного вызова в метод класса, реализующего интерфейс слушателя, создаете экземпляр, и регистрируете экземпляр. Это настолько распространенный сценарий, что многие программисты используют синтаксис «анонимный экземпляр анонимного класса»:
Поскольку Java 8 позиционирует JavaFX в качестве преемника инструментария Swing GUI, я использую JavaFX в этих примерах. Детали не имеют значения. В каждой библиотеке пользовательского интерфейса, будь то Swing, JavaFX или Android, вы передаете кнопке некоторый код, который вы хотите запустить, когда кнопка нажата.
Во всех трех примерах вы видели один и тот же подход. Блок кода кому-то передавался — пулу потоков, методу сортировки или кнопке. Этот код вызывался некоторое время спустя.
До сих пор передача кода не была простой в Java. Вы не могли просто передать блоки кода куда угодно. Java является объектно-ориентированным языком, так что вы должны были создать объект, принадлежащий к классу, у которого есть метод с нужным кодом.
В других языках можно работать с блоками кода непосредственно. Проектировщики Java сопротивлялись добавлению этой функции в течение длительного времени. В конце концов, большая сила Java в ее простоте и последовательности. Язык может стать крайне беспорядочным, если будет включать в себя все функции, которые дают чуть более краткий код. Тем не менее, в тех других языках, это не просто легче порождать поток или зарегистрировать обработчик кнопки щелчка; многие их API проще, более последовательны и мощные. В Java, можно было бы написать подобные интерфейсы, которые принимают объекты классов, реализующих определенную функцию, но такие API было бы неудобно использовать.
В последнее время вопрос был не в том, расширять Java для функционального программирования или нет, а как это сделать. Потребовалось несколько лет экспериментов, прежде чем выяснилось, что это хорошо подходит для Java. В следующем разделе вы увидите, как можно работать с блоками кода в Java 8.
Синтаксис лямбда-выражений
Рассмотрим предыдущий пример сортировки еще раз. Мы передаем код, который проверяет, какая строка короче. Мы вычисляем
Вы только что видели ваше первое лямбда-выражение! Такое выражение является просто блоком кода вместе со спецификацией любых переменных, которые должны быть переданы в код.
Почему такое название? Много лет назад, когда еще не было никаких компьютеров, логик Алонзо Чёрч хотел формализовать, что значит для математической функции быть эффективно вычисляемой. (Любопытно, что есть функции, которые, как известно, существуют, но никто не знает, как вычислить их значения.) Он использовал греческую букву лямбда (λ), чтобы отметить параметры. Если бы он знал о Java API, он написал бы что-то не сильно похожее на то, что вы видели, скорее всего.
Почему буква λ? Разве Чёрч использовал все буквы алфавита? На самом деле, почтенный труд Principia Mathematica использует символ ˆ для обозначения свободных переменных, которые вдохновили Чёрча использовать заглавную лямбда (Λ) для параметров. Но, в конце концов, он переключился на строчной вариант буквы. С тех пор, выражение с переменными параметрами было названо «лямбда-выражение».
Если лямбда-выражение не имеет параметров, вы все равно ставите пустые скобки, так же, как с методом без параметров:
Если типы параметров лямбда-выражения можно вывести, можно опустить их. Например,
Здесь компилятор может сделать вывод, что firstStr и secondStr должны быть строками, потому что лямбда-выражение присваивается компаратору строк. (Мы посмотрим на это присваивание повнимательнее позже.)
Если метод имеет один параметр выводимого типа, вы можете даже опустить скобки:
Вы можете добавить аннотации или модификатор final к параметрам лямбды таким же образом, как и для параметров метода:
Вы никогда не указываете тип результата лямбда-выражения. Это всегда выясняется из контекста. Например, выражение
Функциональные интерфейсы
Вы можете поставить лямбда-выражение всякий раз, когда ожидается объект интерфейса с одним абстрактным методом. Такой интерфейс называется функциональным интерфейсом.
Это преобразование в интерфейсы – это то, что делает лямбда-выражения настолько мощными. Синтаксис короткий и простой. Вот еще один пример:
Этот код очень легко читать.
Наконец, заметим, что checked исключения могут возникнуть при преобразовании лямбды в экземпляр функционального интерфейса. Если тело лямбда-выражения может бросить checked исключение, это исключение должно быть объявлено в абстрактном методе целевого интерфейса. Например, следующее было бы ошибкой:
Поскольку Runnable.run не может бросить исключение, это присваивание является некорректным. Чтобы исправить ошибку, у вас есть два варианта. Вы можете поймать исключение в теле лямбда-выражения. Или вы можете присвоить лямбду интерфейсу, один абстрактный метод которого может бросить исключение. Например, метод call из интерфейса Callable может бросить любое исключение. Таким образом, вы можете присвоить лямбду Callable (если добавить return null ).
Ссылки на методы
В качестве другого примера, предположим, что вы хотите отсортировать строки независимо от регистра букв. Вы можете написать такой код:
Как вы можете видеть из этих примеров оператор :: отделяет имя метода от имени объекта или класса. Есть три основных варианта:
Ссылки на конструктор
Но это неудовлетворительно. Пользователь хочет массив кнопок, а не объектов. Библиотека потоков решает эту проблему за счет ссылок на конструкторы. Передайте Button[]::new методу toArray :
Метод toArray вызывает этот конструктор для получения массива нужного типа. Затем он заполняет и возвращает массив.
Область действия переменной
Часто вы хотели бы иметь возможность получить доступ к переменным из охватывающего метода или класса в лямбда-выражении. Рассмотрим следующий пример:
Если подумать хорошенько, то не очевидно, что здесь происходит. Код лямбда-выражения может выполниться гораздо позже вызова repeatText и переменные параметров уже будут потеряны. Как же переменные text и count остаются доступными?
Чтобы понять, что происходит, мы должны уточнить наши представления о лямбда-выражениях. Лямбда-выражение имеет три компонента:
Техническим термином для блока кода вместе со значениями свободных переменных является замыкание. Если кто-то злорадствует, что их язык поддерживает замыкания, будьте уверены, что Java также их поддерживает. В Java лямбда-выражения являются замыканиями. На самом деле, внутренние классы были замыканиями все это время. Java 8 предоставляет нам замыкания с привлекательным синтаксисом.
Как вы видели, лямбда-выражение может захватить значение переменной в охватывающей области. В Java, чтобы убедиться, что захватили значение корректно, есть важное ограничение. В лямбда-выражении можно ссылаться только на переменные, значения которых не меняются. Например, следующий код является неправильным:
Существует причина для этого ограничения. Изменяющиеся переменные в лямбда-выражениях не потокобезопасны. Рассмотрим последовательность параллельных задач, каждая из которых обновляет общий счетчик.
Если бы этот код был правомерным, это было бы не слишком хорошо. Приращение matchCount++ неатомарно, и нет никакого способа узнать, что произойдет, если несколько потоков выполнят этот код одновременно.
Внутренние классы могут также захватывать значения из охватывающей области. До Java 8 внутренние классы могли иметь доступ только к локальным final переменным. Это правило теперь ослаблено для соответствия правилу для лямбда-выражений. Внутренний класс может получить доступ к любой эффективно final локальной переменной; то есть, к любой переменной, значение которой не изменяется.
Не рассчитывайте, что компилятор выявит все параллельные ошибки доступа. Запрет на модификацию имеет место только для локальных переменных. Если matchCount – переменная экземпляра или статическая переменная из охватывающего класса, то никакой ошибки не будет, хотя результат так же не определен.
Кроме того, совершенно законно изменять разделяемый объект, хоть это и не очень надежно. Например,
Существуют безопасные механизмы подсчета и сбора значений одновременно. Вы можете использовать потоки для сбора значений с определенными свойствами. В других ситуациях вы можете использовать потокобезопасные счетчики и коллекции.
Как и с внутренними классами, есть обходное решение, которое позволяет лямбда-выражению обновить счетчик в локальной охватывающей области видимости. Используйте массив длиной 1, вроде этого:
Конечно, такой код не потокобезопасный. Для обратного вызова кнопки это не имеет значения, но в целом, вы должны подумать дважды, прежде чем использовать этот трюк.
Тело лямбда-выражения имеет ту же область видимости, что и вложенный блок. Здесь применяются те же самые правила для конфликтов имен. Нельзя объявить параметр или локальную переменную в лямбде, которые имеют то же имя, что и локальная переменная.
Внутри метода вы не можете иметь две локальные переменные с тем же именем. Таким образом, вы не можете объявить такие переменные также и в лямбда-выражении. При использовании ключевого слова this в лямбда-выражении вы ссылаетесь на параметр this метода, который создает лямбду. Рассмотрим, например, следующий код
Методы по умолчанию
Многие языки программирования интегрируют функциональные выражения с их библиотеками коллекций. Это часто приводит к коду, который короче и проще для понимания, чем эквивалент, использующий циклы. Например, рассмотрим цикл:
Рассмотрим такой интерфейс:
Что произойдет, если вы создадите класс, реализующий оба?
Теперь предположим, что Naming интерфейс не содержит реализацию по умолчанию для getFirstName :
Если ни один интерфейс не обеспечивает реализацию по умолчанию для общего метода, то мы находимся в пре-Java 8 ситуации и нет никакого конфликта. У класса реализации есть две возможности: реализовать метод или оставить его нереализованным. В последнем случае класс сам является абстрактным.
Мы только что обсудили конфликты имен между двумя интерфейсами. Теперь рассмотрим класс, расширяющий суперкласс и реализующий интерфейс, наследуя тот же метод от обоих. Например, предположим, что Person является классом и Student определяется как: