Spec-Zone.ru › Kotlin 2

Функции высшего порядка и лямбда-выражения

Функции 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).

Стрелочная нотация является правоассоциативной: (Int) -> (Int) -> Unit эквивалентно предыдущему примеру, но не ((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
}
18 ноября 2025 г.
ФункцииТипобезопасные построители

© 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

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API