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

Обновлено: 16.05.2024

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

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

Функции высшего порядка

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

Хорошим примером такой функции является идиома функционального программирования fold для коллекций, которая принимает начальное значение - accumulator вместе с комбинирующей функцией и строит возвращаемое значение, последовательно комбинируя текущее значение accumulator с каждым элементом коллекции, заменяя значение accumulator :

В приведённом выше коде параметр combine имеет функциональный тип (R, T) -> R , поэтому он принимает функцию, которая принимает два аргумента типа R и T и возвращает значение типа R . Он вызывается внутри цикла for и присваивает accumulator возвращаемое значение.

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

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

Функциональные типы

String` for declarations that deal with functions: `val onClick: () -> Unit = . `. -->

Kotlin использует семейство функциональных типов, таких как (Int) -> String , для объявлений, которые являются частью функций: val onClick: () -> Unit = . .

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

  • У всех функциональных типов есть список с типами параметров, заключенный в скобки, и возвращаемый тип: (A, B) -> C обозначает тип, который предоставляет функции два принятых аргумента типа A и B , а также возвращает значение типа C . Список с типами параметров может быть пустым, как, например, в () -> A . Возвращаемый тип Unit не может быть опущен.
  • У функциональных типов может быть дополнительный тип - получатель (receiver), который указывается в объявлении перед точкой: тип A.(B) -> C описывает функции, которые могут быть вызваны для объекта-получателя A с параметром B и возвращаемым значением C . Литералы функций с объектом-приёмником часто используются вместе с этими типами.
    (suspending functions) принадлежат к особому виду функциональных типов, у которых в объявлении присутствует модификатор suspend, например, suspend () -> Unit или suspend A.(B) -> C .

Объявление функционального типа также может включать именованные параметры: (x: Int, y: Int) -> Point . Именованные параметры могут быть использованы для описания смысла каждого из параметров.

Чтобы указать, что функциональный тип может быть nullable, используйте круглые скобки: ((Int, Int) -> Int)? .

При помощи круглых скобок функциональные типы можно объединять: (Int) -> ((Int) -> Unit) .

Стрелка в объявлении является правоассоциативной (right-associative), т.е. объявление (Int) -> (Int) -> Unit эквивалентно объявлению из предыдущего примера, а не ((Int) -> (Int)) -> Unit .

Вы также можете присвоить функциональному типу альтернативное имя, используя псевдонимы типов:

Создание функционального типа

Существует несколько способов получить экземпляр функционального типа:

Используя блок с кодом внутри функционального литерала в одной из форм:

Литералы функций с объектом-приёмником могут использоваться как значения функциональных типов с получателем.

Используя вызываемую ссылку на существующее объявление:

  • функции верхнего уровня, локальной функции, функции-члена или функции-расширения: ::isOdd , String::toInt ,
  • свойства верхнего уровня, члена или свойства-расширения: List<Int>::size , : ::Regex

К ним относятся привязанные вызываемые ссылки, которые указывают на член конкретного экземпляра: foo::toString .

  • Используя экземпляр пользовательского класса, который реализует функциональный тип в качестве интерфейса:

При достаточной информации компилятор может самостоятельно вывести функциональный тип для переменной:

C` can be passed or assigned where a `A.(B) -> C` is expected and the other way around: -->

Небуквальные (non-literal) значения функциональных типов с и без получателя являются взаимозаменяемыми, таким образом получатель может заменить первый параметр, и наоборот. Например, значение типа (A, B) -> C может быть передано или назначено там, где ожидается A.(B) -> C , и наоборот.

Note that a function type with no receiver is inferred by default, even if a variable is initialized with a reference > to an extension function. > To alter that, specify the variable type explicitly. -->

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

Вызов экземпляра функционального типа

Значение функционального типа может быть вызвано с помощью оператора invoke(. ) : f.invoke(x) или просто f(x) .

Если значение имеет тип получателя, то объект-приёмник должен быть передан в качестве первого аргумента. Другой способ вызвать значение функционального типа с получателем - это добавить его к объекту-приёмнику, как если бы это была функция-расширение: 1.foo(2) ,

Встроенные функции (Inline functions)

Иногда выгодно улучшить производительность функций высшего порядка, используя встроенные функции.

Лямбда-выражения и анонимные функции

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

Функция max является функцией высшего порядка, потому что она принимает функцию в качестве второго аргумента. Этот второй аргумент является выражением, которое в свою очередь есть функция, то есть функциональный литерал. Как функция он эквивалентен объявлению:

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

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

sign. If the inferred return type of the lambda is not Unit, the last (or possibly single) expression inside the lambda body is treated as the return value.-->

Лямбда-выражение всегда заключено в скобки <. >, объявление параметров при таком синтаксисе происходит внутри этих скобок и может включать в себя аннотации типов (опционально), тело функции начинается после знака -> . Если тип возвращаемого значения не Unit , то в качестве возвращаемого типа принимается последнее (а возможно и единственное) выражение внутри тела лямбды.

Если мы вынесем все необязательные объявления, то, что останется, будет выглядеть следующим образом:

Передача лямбды в качестве последнего параметра

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

Такой синтаксис также известен как trailing lambda.

Когда лямбда-выражение является единственным аргументом функции, круглые скобки могут быть опущены:

Ключевое слово it : неявное имя единственного параметра

Очень часто лямбда-выражение имеет только один параметр.

`. The parameter will be implicitly declared under the name `it`: -->

Если компилятор способен самостоятельно определить сигнатуру, то объявление параметра можно опустить вместе с -> . Параметр будет неявно объявлен под именем it :

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

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

Таким образом, два следующих фрагмента равнозначны:

Это соглашение, вместе с передачей лямбда-выражения вне скобок, позволяет писать код в стиле LINQ:

Символ подчеркивания для неиспользуемых переменных (since 1.1)

Если параметр лямбды не используется, то разрешено его имя заменить на символ подчёркивания:

Деструктуризация в лямбдах (since 1.1)

Деструктуризация в лямбдах описана в деструктурирующие объявления.

Анонимные функции

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

Объявление анонимной функции выглядит очень похоже на обычное объявление функции, за исключением того, что её имя опущено. Тело такой функции может быть описано и выражением (как показано выше), и блоком:

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

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

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

Одним из отличий лямбда-выражений от анонимных функций является поведение оператора return (non-local returns). Слово return , не имеющее метки ( @ ), всегда возвращается из функции, объявленной ключевым словом fun. Это означает, что return внутри лямбда-выражения возвратит выполнение к функции, включающей в себя это лямбда-выражение. Внутри анонимных функций оператор return, в свою очередь, выйдет, собственно, из анонимной функции.

Замыкания

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

Литералы функций с объектом-приёмником

C`, can be instantiated with a special form of function literals – function literals with receiver. -->

Функциональные типы с получателем, такие как A.(B) -> C , могут быть вызваны с помощью особой формы - литералов функций с объектом-приёмником.

Как было сказано выше, Kotlin позволяет вызывать экземпляр функционального типа с получателем, предоставляющим объект-приёмник.

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

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

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

Синтаксис анонимной функции позволяет вам явно указать тип приёмника. Это может быть полезно в случае, если вам нужно объявить переменную типа нашей функции для использования в дальнейшем.

Лямбда-выражения могут быть использованы как литералы функций с приёмником, когда тип приёмника может быть выведен из контекста. Один из самых важных примеров их использования это типобезопасные строители (type-safe builders):

Читайте также: