type | layout | title | url |
---|---|---|---|
doc |
reference |
Лямбды |
Высокоуровневая функция - это функция, которая принимает другую функцию в качестве входного аргумента,
либо имеет функцию в качестве возвращаемого результата. Хорошим примером такой функции является lock()
,
которая берёт залоченный объект и функцию, применяет лок, выполняет функцию и отпускает lock
:
fun <T> lock(lock: Lock, body: () -> T): T{
lock.lock()
try{
return body()
}
finally {
lock.unlock()
}
}
Давайте проанализируем этот блок. Параметр body
имеет функциональный тип: () -> T
, то есть предполагается,
что это функция, которая не имеет никаких входных аргументов и возвращает значение типа T
. Она вызывается
внутри блока try
, защищена lock
, и её результат возвращается функцией lock()
.
Если мы хотим вызвать метод lock(), мы можем подать другую функцию в качестве входящего аргумента (более подробно читайте function references)
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.lenght == 5 }.sortBy { it }.map { it.toUpperCase() }
Иногда необходимо улучшить производительность высокоуровневых функций, используя инлайн функции
Лямбда-выражения или анонимные функции являются "функциональными константами"(ориг. "functional literal"), то есть функциями, которые не были объявлены, но сразу были переданы в качестве выражения. Рассмотрим следующий пример:
max(strings, { a, b -> a.length < b.length })
Функция max
- высокоуровневая функция, так как она принимает другую функцию в качестве входного аргумента. Этот второй аргумент является выражением, которое само по себе представляет из себя функцию, то есть functional literal.
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() // вызов метода объекта-приёмника
}