type | layout | title | url |
---|---|---|---|
doc |
reference |
Лямбды |
Функция высшего порядка - это функция, которая принимает функции как параметры, или возвращает функцию в качестве результата.
Хорошим примером такой функции является lock()
, которая берёт блокирующий объект и функцию, получает блокировку, выполняет функцию и отпускает блокировку:
fun <T> lock(lock: Lock, body: () -> T): T{
lock.lock()
try{
return body()
}
finally {
lock.unlock()
}
}
Проанализируем этот код: body
имеет функциональный тип: () -> T
, то есть параметр должен быть
функцией без параметров, возвращающей значение типа T
. Она вызывается
внутри блока try
, под защитой объекта lock
, получившего блокировку вызовом функции lock()
.
Если мы хотим вызвать метод lock()
, можно передать другую функцию в качестве входящего аргумента
(подробно читайте Ссылки на функции):
fun toBeSynchronized() = sharedResource.operation()
val result = lock (lock, ::toBeSynchronized)
Обычно удобней передавать лямбда-выражения:
val result = lock(lock, { sharedResource.operation() })
Лямбда-выражения подробно описаны здесь, но ради продолжения этого раздела сделаем краткий обзор:
- Лямбда-выражение всегда заключено в фигурные скобки;
- Его параметры (если они есть) объявлены до знака
->
(допустимо не указывать параметры); - Тело выражения следует после знака
->
.
В Kotlin есть конвенция, согласно которой, если последний параметр функции является функцией, которая передается в виде лямбда-выражения, можно вынести его за скобки:
lock (lock) {
sharedResource.operation()
}
Следующим примером функции высшего порядка выберем функцию map()
:
fun <T, R> List<T>.map(transform: (T) -> R): List<R> {
val result = arrayListOf<R>()
for (item in this)
result.add(transform(item))
return result
}
Эту функцию можно вызвать так:
val doubled = ints.map { it -> it * 2 }
Обратите внимание, что скобки можно вообще не указывать при вызове функции, если лямбда является единственным аргументом.
Ещё одна полезная конвенция состоит в том, что если функциональный литерал имеет ровно один параметр,
его объявление можно удалить (вместе с ->
), и обращаться к нему по имени it
:
ints.map { it * 2 }
Эти конвенции позволяют писать код в стиле LINQ:
strings.filter { it.length == 5 }.sortBy { it }.map { it.toUpperCase() }
Если параметр лямбды не используется, разрешено применять подчеркивание вместо его имени
map.forEach { _, value -> println("$value!") }
Деструктуризация в лямбдах описана в деструктурирующие объявления.
Иногда выгодно улучшить производительность функций высшего порядка, используя инлайн функции.
Лямбда-выражение, или анонимная функция, это "функциональный литерал", то есть необъявленная функция, которая немедленно используется в качестве выражения. Рассмотрим следующий пример:
max(strings, { a, b -> a.length < b.length })
Функция max
является функцией высшего порядка, потому что она принимает функцию в качестве второго аргумента.
Этот второй аргумент является выражением, которое в свою очередь есть функция, то есть функциональный литерал.
Как функция он эквивалентен объявлению:
fun compare(a: String, b: String): Boolean = a.length < b.length
Чтобы функция могла принять функцию в качестве параметра, необходимо указать тип функции-параметра.
Например, вышеуказанная функция max
определена так:
fun <T> max(collection: Collection<T>, less: (T, T) -> Boolean): T? {
var max: T? = null
for (it in collection)
if (max == null || less(max, it))
max = it
return max
}
Параметр less
является (T, T) -> Boolean
типом, то есть функцией, которая принимает два параметра типа T
и возвращает Boolean: 'true',
если первый параметр меньше второго.
В теле функции, строка 4, less
используется как функция: она вызвана с двумя параметрами типа T
.
Функциональный тип записывается, как указано выше, или может иметь именованые параметры, если нужно выявить смысл каждого из параметров.
val compare: (x: T, y: T) -> Int = ...
Полная синтаксическая форма лямбда-выражений, таких как literals of function types, может быть представлена следующим образом:
val sum = { x: Int, y: Int -> x + y }
Лямбда-выражение всегда заключено в скобки {...}
, объявление параметров при таком синтаксисе происходит внутри этих скобок и может включать в себя аннотации типов (опционально), тело функции начинается после знака ->
. Если тип возвращаемого значения не Unit
, то в качестве возвращаемого типа принимается последнее (а возможно и единственное) выражение внутри тела лямбды.
Если мы вынесем все необязательные объявления, то, что останется, будет выглядеть следующим образом:
val sum: (Int, Int) -> Int = { x, y -> x + y }
Обычное дело, когда лямбда-выражение имеет только один параметр. Если Kotlin может определить сигнатуру метода сам, он позволит нам не объявлять этот единственный параметр, и объявит его сам под именем it
:
ints.filter { it > 0 } //Эта константа имеет тип '(it: Int) -> Boolean'
Мы можем явно вернуть значение из лямбды, используя qualified return синтаксис:
ints.filter {
val shouldFilter = it > 0
shouldFilter
}
ints.filter {
val shouldFilter = it > 0
return@filter shouldFilter
}
Обратите внимение, что функция принимает другую функцию в качестве своего последнего параметра, аргумент лямбда-выражения в таком случае может быть принят вне списка аргументов, заключённого в скобках. См. callSuffix.
Единственной особенностью синтаксиса лямбда-выражений, о которой ещё не было сказано, является способность определять и назначать возвращаемый функцией тип. В большинстве случаев в этом нет особой необходимости, потому что он может быть вычислен автоматически. Однако, если у вас есть потребность в определении возвращаемого типа, вы можете воспользоваться альтернативным синтаксисом:
fun(x: Int, y: Int): Int = x + y
Объявление анонимной функции выглядит очень похоже на обычное объявление функции, за исключением того, что её имя опущено. Тело такой функции может быть описано и выражением (как показано выше), и блоком:
fun(x: Int, y: Int): Int {
return x + y
}
Параметры функции и возвращаемый тип обозначаются таким же образом, как в обычных функциях. Правда, тип параметра может быть опущен, если его значение следует из контекста:
ints.filter(fun(item) = item > 0)
Аналогично и с типом возвращаемого значения: он вычисляется автоматически для функций-выражений или же должен быть определён вручную (если не является типом Unit
) для анонимных функций, которые имеют в себе блок.
Обратите внимание, что параметры анонимных функций всегда заключены в круглые скобки (...)
. Приём, позволяющий оставлять параметры вне скобок, работает только с лямбда-выражениями.
Одним из отличий лямбда-выражений от анонимных функций является поведение оператора return
(non-local returns). Слово return
, не имеющее метки (@
), всегда возвращается из функции, объявленной ключевым словом fun
. Это означает, что return
внутри лямбда-выражения возвратит выполнение к функции, включающей в себя это лямбда-выражение. Внутри анонимных функций оператор return
, в свою очередь, выйдет, собственно, из анонимной функции.
Лямбда-выражение или анонимная функция (так же, как и локальная функция или object expression) имеет доступ к своему замыканию, то есть к переменным, объявленным вне этого выражения или функции. В отличие от Java, переменные, захваченные в замыкании, могут быть изменены:
var sum = 0
ints.filter { it > 0 }.forEach {
sum += it
}
print(sum)
Kotlin предоставляет возможность вызывать литерал функции с указаным объектом-приёмником. Внутри тела литерала вы можете вызывать методы объекта-приёмника без дополнительных определителей. Это схоже с принципом работы расширений, которые позволяют получить доступ к членам объекта-приёмника внутри тела функции. Один из самых важных примеров использования литералов с объектом-приёмником это Type-safe Groovy-style builders.
Тип такого литерала — это тип функции с приёмником:
sum : Int.(other: Int) -> Int
По аналогии с расширениями, литерал функции может быть вызван так, будто он является методом объекта-приёмника:
1.sum(2)
Синтаксис анонимной функции позволяет вам явно указать тип приёмника. Это может быть полезно в случае, если вам нужно объявить переменную типа нашей функции для использования в дальнейшем.
val sum = fun Int.(other: Int): Int = this + other
Лямбда-выражения могут быть использованы как литералы функций с приёмником, когда тип приёмника может быть выведен из контекста.
class HTML {
fun body() { ... }
}
fun html(init: HTML.() -> Unit): HTML {
val html = HTML() // создание объекта-приёмника
html.init() // передача приёмника в лямбду
return html
}
html { // лямбда с приёмником начинается тут
body() // вызов метода объекта-приёмника
}