Функции высшего порядка и лямбда-выражения
Функции Kotlin — это объекты первого класса, то есть их можно хранить в переменных и структурах данных, а также передавать в качестве аргументов другим функциям высшего порядка и возвращать из них. С функциями можно выполнять любые операции, доступные для других значений, не являющихся функциями.
Чтобы обеспечить такую возможность, Kotlin как язык программирования со статической типизацией использует семейство функциональных типов для представления функций и предоставляет набор специализированных конструкций языка, таких как лямбда-выражения.
Функции высшего порядка
Функция высшего порядка — это функция, которая принимает функции в качестве параметров или возвращает функцию.
Хороший пример функции высшего порядка — идиома функционального программирования fold для коллекций. Она принимает начальное значение аккумулятора и функцию объединения, а затем формирует возвращаемое значение, последовательно объединяя текущее значение аккумулятора с каждым элементом коллекции и каждый раз заменяя значение аккумулятора:
fun <T, R> Collection<T>.fold(
initial: R,
combine: (acc: R, nextElement: T) -> R
): R {
var accumulator: R = initial
for (element: T in this) {
accumulator = combine(accumulator, element)
}
return accumulator
}
В приведённом выше коде параметр combine имеет функциональный тип (R, T) -> R, поэтому он принимает функцию, которая получает два аргумента типов R и T и возвращает значение типа R. Она вызывается внутри цикла for, а возвращаемое значение затем присваивается accumulator.
Чтобы вызвать fold, нужно передать ей в качестве аргумента экземпляр функционального типа. Для этого в местах вызова функций высшего порядка широко используются лямбда-выражения (подробнее о них ниже):
fun main() {
//sampleStart
val items = listOf(1, 2, 3, 4, 5)
// Lambdas are code blocks enclosed in curly braces.
items.fold(0, {
// When a lambda has parameters, they go first, followed by '->'
acc: Int, i: Int ->
print("acc = $acc, i = $i, ")
val result = acc + i
println("result = $result")
// The last expression in a lambda is considered the return value:
result
})
// Parameter types in a lambda are optional if they can be inferred:
val joinedToString = items.fold("Elements:", { acc, i -> acc + " " + i })
// Function references can also be used for higher-order function calls:
val product = items.fold(1, Int::times)
//sampleEnd
println("joinedToString = $joinedToString")
println("product = $product")
}
Функциональные типы
В Kotlin для объявлений, связанных с функциями, используются функциональные типы, например (Int) -> String: val onClick: () -> Unit = ....
Эти типы имеют специальную нотацию, соответствующую сигнатурам функций — их параметрам и возвращаемым значениям:
У всех функциональных типов есть заключённый в скобки список типов параметров и тип возвращаемого значения:
(A, B) -> Cобозначает тип, представляющий функции, которые принимают два аргумента типовAиBи возвращают значение типаC. Список типов параметров может быть пустым, как в() -> A. Тип возвращаемого значенияUnitнельзя опускать.Функциональные типы могут дополнительно иметь тип приёмника, который указывается перед точкой в нотации: тип
A.(B) -> Cпредставляет функции, которые можно вызывать для объекта-приёмникаA, передавая параметрBи получая значениеC. Вместе с такими типами часто используются функциональные литералы с приёмником.Приостанавливаемые функции относятся к особому виду функциональных типов, в нотации которых используется модификатор suspend, например
suspend () -> Unitилиsuspend A.(B) -> C.
Нотация функционального типа может дополнительно включать имена параметров функции: (x: Int, y: Int) -> Point. Эти имена можно использовать для пояснения назначения параметров.
Чтобы указать, что функциональный тип является nullable, используйте скобки следующим образом: ((Int, Int) -> Int)?.
Функциональные типы также можно объединять с помощью скобок: (Int) -> ((Int) -> Unit).
Функциональному типу также можно дать альтернативное имя с помощью псевдонима типа:
typealias ClickHandler = (Button, ClickEvent) -> Unit
Создание экземпляра функционального типа
Существует несколько способов получить экземпляр функционального типа:
-
Использовать блок кода в функциональном литерале в одной из следующих форм:
лямбда-выражение:
{ a, b -> a + b },анонимная функция:
fun(s: String): Int { return s.toIntOrNull() ?: 0 }
Функциональные литералы с приёмником можно использовать в качестве значений функциональных типов с приёмником.
-
Использовать ссылку на вызываемый объект для существующего объявления:
функцию верхнего уровня, локальную, функцию-член или функцию-расширение функцию:
::isOdd,String::toInt,свойство верхнего уровня, свойство-член или свойство-расширение свойство:
List<Int>::size,конструктор:
::Regex
К ним относятся связанные ссылки на вызываемые объекты, указывающие на член определённого экземпляра:
foo::toString. Использовать экземпляры пользовательского класса, реализующего функциональный тип в качестве интерфейса:
class IntTransformer: (Int) -> Int {
override operator fun invoke(x: Int): Int = TODO()
}
val intFunction: (Int) -> Int = IntTransformer()
Компилятор может вывести функциональные типы переменных, если для этого достаточно информации:
val a = { i: Int -> i + 1 } // The inferred type is (Int) -> Int
Нелитеральные значения функциональных типов с приёмником и без него взаимозаменяемы: приёмник может выступать вместо первого параметра, и наоборот. Например, значение типа (A, B) -> C можно передать или присвоить там, где ожидается значение типа A.(B) -> C, и наоборот:
fun main() {
//sampleStart
val repeatFun: String.(Int) -> String = { times -> this.repeat(times) }
val twoParameters: (String, Int) -> String = repeatFun // OK
fun runTransformation(f: (String, Int) -> String): String {
return f("hello", 3)
}
val result = runTransformation(repeatFun) // OK
//sampleEnd
println("result = $result")
}
Вызов экземпляра функционального типа
Значение функционального типа можно вызвать с помощью его оператора invoke(...): f.invoke(x) или просто f(x).
Если у значения есть тип приёмника, объект-приёмник следует передать в качестве первого аргумента. Другой способ вызвать значение функционального типа с приёмником — поставить перед ним объект-приёмник, как если бы это значение было функцией-расширением: 1.foo(2).
Пример:
fun main() {
//sampleStart
val stringPlus: (String, String) -> String = String::plus
val intPlus: Int.(Int) -> Int = Int::plus
println(stringPlus.invoke("<-", "->"))
println(stringPlus("Hello, ", "world!"))
println(intPlus.invoke(1, 1))
println(intPlus(1, 2))
println(2.intPlus(3)) // extension-like call
//sampleEnd
}
Встраиваемые функции
Для функций высшего порядка иногда полезно использовать встраиваемые функции, обеспечивающие гибкое управление потоком выполнения.
Лямбда-выражения и анонимные функции
Лямбда-выражения и анонимные функции — это функциональные литералы. Функциональные литералы — это функции, которые не объявляются, а сразу передаются в виде выражения. Рассмотрим следующий пример:
max(strings, { a, b -> a.length < b.length })
Функция max — это функция высшего порядка, поскольку она принимает значение функции в качестве второго аргумента. Этот второй аргумент — выражение, которое само является функцией, называемой функциональным литералом. Оно эквивалентно следующей именованной функции:
fun compare(a: String, b: String): Boolean = a.length < b.length
Также можно создать приостанавливаемое лямбда-выражение с помощью ключевого слова suspend. Приостанавливаемое лямбда-выражение имеет функциональный тип suspend () -> Unit и может вызывать другие приостанавливаемые функции:
val suspendingTask = suspend { doSuspendingWork() }
Синтаксис лямбда-выражений
Полная синтаксическая форма лямбда-выражений выглядит так:
val sum: (Int, Int) -> Int = { x: Int, y: Int -> x + y }
Лямбда-выражение всегда заключается в фигурные скобки.
В полной синтаксической форме объявления параметров располагаются внутри фигурных скобок и могут содержать необязательные аннотации типов.
Тело располагается после
->.Если выведенный тип возвращаемого значения лямбда-выражения не
Unit, последнее (или единственное) выражение в теле лямбда-выражения рассматривается как возвращаемое значение.
Если опустить все необязательные аннотации, останется такая форма:
val sum = { x: Int, y: Int -> x + y }
Передача лямбда-выражений в конце вызова
Согласно соглашению Kotlin, если последний параметр функции является функцией, лямбда-выражение, передаваемое в качестве соответствующего аргумента, можно разместить за скобками:
val product = items.fold(1) { acc, e -> acc * e }
Такой синтаксис также называют лямбда-выражением в конце вызова.
Если лямбда-выражение — единственный аргумент вызова, скобки можно опустить полностью:
run { println("...") }
it: неявное имя единственного параметра
Лямбда-выражения очень часто имеют только один параметр.
Если компилятор может разобрать сигнатуру без параметров, параметр не нужно объявлять, а -> можно опустить. Параметр будет неявно объявлен под именем it:
ints.filter { it > 0 } // this literal is of type '(it: Int) -> Boolean'
Возврат значения из лямбда-выражения
Из лямбда-выражения можно явно вернуть значение с помощью синтаксиса квалифицированного оператора return. В противном случае последнее выражение неявно возвращается.
Таким образом, следующие два фрагмента кода эквивалентны:
ints.filter {
val shouldFilter = it > 0
shouldFilter
}
ints.filter {
val shouldFilter = it > 0
return@filter shouldFilter
}
Это соглашение вместе с передачей лямбда-выражения за скобками позволяет писать код в стиле LINQ:
strings.filter { it.length == 5 }.sortedBy { it }.map { it.uppercase() }
Подчёркивание для неиспользуемых переменных
Если параметр лямбда-выражения не используется, вместо его имени можно указать подчёркивание:
map.forEach { (_, value) -> println("$value!") }
Деструктуризация в лямбда-выражениях
Деструктуризация в лямбда-выражениях описана в разделе объявлений с деструктуризацией.
Анонимные функции
В приведённом выше синтаксисе лямбда-выражений не хватает одной возможности — указать тип возвращаемого значения функции. В большинстве случаев в этом нет необходимости, поскольку тип возвращаемого значения можно вывести автоматически. Однако если его нужно указать явно, можно использовать альтернативный синтаксис — анонимную функцию.
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 без метки всегда выполняет возврат из функции, объявленной с помощью ключевого слова fun. Это означает, что return внутри лямбда-выражения выполнит возврат из охватывающей функции, тогда как return внутри анонимной функции выполнит возврат из самой анонимной функции.
Замыкания
Лямбда-выражение или анонимная функция (а также локальная функция и объектное выражение) могут обращаться к своему замыканию, которое включает переменные, объявленные во внешней области видимости. Захваченные замыканием переменные можно изменять в лямбда-выражении:
var sum = 0
ints.filter { it > 0 }.forEach {
sum += it
}
print(sum)
Функциональные литералы с приёмником
Функциональные типы с приёмником, например A.(B) -> C, можно создавать с помощью особой формы функциональных литералов — функциональных литералов с приёмником.
Как упоминалось выше, Kotlin позволяет вызывать экземпляр функционального типа с приёмником, передавая объект-приёмник.
В теле функционального литерала объект-приёмник, переданный при вызове, становится неявным this. Благодаря этому можно обращаться к членам объекта-приёмника без дополнительных квалификаторов или использовать объект-приёмник с помощью выражения this.
Такое поведение похоже на поведение функций-расширений, которые также позволяют обращаться к членам объекта-приёмника из тела функции.
Вот пример функционального литерала с приёмником и его типа, где plus вызывается для объекта-приёмника:
val sum: Int.(Int) -> Int = { other -> plus(other) }
Синтаксис анонимных функций позволяет напрямую указывать тип приёмника функционального литерала. Это может быть полезно, если нужно объявить переменную функционального типа с приёмником, а затем использовать её.
val sum = fun Int.(other: Int): Int = this + other
Лямбда-выражения можно использовать в качестве функциональных литералов с приёмником, если тип приёмника можно вывести из контекста. Один из наиболее важных примеров их использования — типобезопасные построители:
class HTML {
fun body() { ... }
}
fun html(init: HTML.() -> Unit): HTML {
val html = HTML() // create the receiver object
html.init() // pass the receiver object to the lambda
return html
}
html { // lambda with receiver begins here
body() // calling a method on the receiver object
}
© 2010–2026 JetBrains s.r.o. and Kotlin Programming Language contributors
Licensed under the Apache License, Version 2.0.
https://kotlinlang.org/docs/lambdas.html