--- title: Лямбды и функциональные типы description: Синтаксис лямбд, it, функциональные типы, ссылки на функции и анонимные функции tags: - kotlin - functional --- Функциональный тип описывает функцию как значение: `(параметры) -> результат`. ```kotlin val square: (Int) -> Int = { x -> x * x } val greet: (String) -> String = { "Привет, $it" } val action: () -> Unit = { println("сделано") } val sum: (Int, Int) -> Int = { a, b -> a + b } fun main() { println(square(4)) // 16 println(greet("мир")) // Привет, мир action() // сделано println(sum(2, 3)) // 5 } ``` ## Синтаксис лямбды Лямбда всегда в фигурных скобках: ```kotlin { параметры -> тело } ``` ```kotlin val f = { x: Int, y: Int -> x + y } // типы указаны в лямбде val g: (Int) -> Int = { it * 2 } // тип указан у переменной ``` Тип нужно указать либо у переменной, либо у параметров лямбды - где-то компилятор должен его увидеть. Результат - **последнее выражение** тела, `return` не нужен и в обычной лямбде запрещён: ```kotlin val describe: (Int) -> String = { n -> val prefix = if (n > 0) "плюс" else "минус" "$prefix $n" // это и есть результат } fun main() { println(describe(5)) } // плюс 5 ``` ## it - неявный параметр Если параметр один, его можно не объявлять: ```kotlin val double: (Int) -> Int = { it * 2 } println(listOf(1, 2, 3).map { it * 10 }) // [10, 20, 30] ``` `it` удобен в короткой лямбде. Если тело длинное или лямбды вложены - давайте имя: ```kotlin val users = listOf("Иван", "Пётр") println(users.map { name -> name.uppercase() }) // [ИВАН, ПЁТР] ``` ## Лямбда последним аргументом Если последний параметр функции - функция, лямбду выносят за скобки: ```kotlin fun measure(name: String, block: () -> Unit) { val start = System.nanoTime() block() println("$name занял ${System.nanoTime() - start} нс") } fun main() { measure("цикл") { // лямбда вне скобок repeat(1000) { } } measure("пусто", { }) // так тоже можно, но не принято } ``` Когда функция принимает **только** лямбду, круглые скобки опускаются совсем. Именно на этом правиле построены `forEach`, `let`, `apply` и все DSL: ```kotlin listOf(1, 2).forEach { println(it) } ``` Подробнее: [DSL](../../Функциональный%20Kotlin/DSL.md) ## Неиспользуемые параметры ```kotlin val map = mapOf("a" to 1) map.forEach { (_, value) -> println(value) } // 1 — ключ не нужен ``` Подчёркивание говорит компилятору, что параметр намеренно игнорируется. ## Ссылки на функции - :: Вместо лямбды, которая только вызывает существующую функцию, используют ссылку: ```kotlin fun isEven(n: Int) = n % 2 == 0 fun main() { val nums = listOf(1, 2, 3, 4) println(nums.filter { isEven(it) }) // [2, 4] — лямбда-обёртка println(nums.filter(::isEven)) // [2, 4] — ссылка, короче nums.forEach(::println) } ``` Виды ссылок: ```kotlin class User(val name: String) { fun greet() = "Привет, $name" } fun main() { val byName: (User) -> String = User::name // ссылка на свойство val greeter: (User) -> String = User::greet // на метод класса val factory: (String) -> User = ::User // на конструктор val u = User("Иван") println(byName(u)) // Иван println(greeter(u)) // Привет, Иван println(factory("Пётр").name) // Пётр val bound = u::greet // привязанная к объекту println(bound()) // Привет, Иван } ``` ## Анонимные функции Когда нужен явный `return` или явный тип результата: ```kotlin val parse = fun(s: String): Int? { if (s.isBlank()) return null // return работает return s.toIntOrNull() } fun main() { println(parse(" ")) // null println(parse("42")) // 42 } ``` Отличие от лямбды: `return` выходит из самой анонимной функции, а в лямбде он запрещён (или выходит из внешней функции, если лямбда inline). ## Функциональные типы в подробностях ```kotlin val a: () -> Unit // без параметров, без результата val b: (Int) -> String // один параметр val c: (Int, Int) -> Int // два параметра val d: ((Int) -> Int) -> Int // принимает функцию val e: (Int) -> ((Int) -> Int) // возвращает функцию val f: String.(Int) -> String // с получателем (receiver) val g: ((Int) -> Int)? // nullable val h: suspend () -> Unit // suspend-функция ``` Тип с получателем вызывается как метод: ```kotlin val repeatTimes: String.(Int) -> String = { n -> this.repeat(n) } fun main() { println("ab".repeatTimes(2)) // abab } ``` На этом построены `apply`, `buildString` и DSL. Подробнее: [Receivers и context parameters](../../Функциональный%20Kotlin/Receivers%20и%20context%20parameters.md) ## Замыкания Лямбда захватывает переменные из окружения - **включая изменяемые**: ```kotlin fun counter(): () -> Int { var count = 0 // захвачена по ссылке return { ++count } } fun main() { val next = counter() println(next()) // 1 println(next()) // 2 println(next()) // 3 } ``` Это важное отличие от Java, где захватить можно только effectively final переменную. ```kotlin fun main() { var sum = 0 listOf(1, 2, 3).forEach { sum += it } // в Java так нельзя println(sum) // 6 } ``` ## Вызов ```kotlin val f: (Int) -> Int = { it + 1 } fun main() { println(f(1)) // 2 — обычный вызов println(f.invoke(1)) // 2 — то же самое val g: ((Int) -> Int)? = null println(g?.invoke(1)) // null — безопасный вызов nullable-функции } ``` ## Что происходит под капотом Функциональный тип компилируется в интерфейс `FunctionN`: ```kotlin val f: (Int) -> Int = { it * 2 } // объект класса Function1 ``` Это значит аллокацию на каждую лямбду, если она захватывает переменные. Для функций высшего порядка в горячем коде существует `inline` - он подставляет тело лямбды в место вызова, и объекта не создаётся вовсе. Подробнее: [Inline-функции](../../Функциональный%20Kotlin/Inline-функции.md), [Kotlin в байт-коде](../../Взаимодействие%20с%20Java/Kotlin%20в%20байт-коде.md) > [!info] Отличия от Java > | Java | Kotlin | > |---|---| > | `x -> x * 2` | `{ x -> x * 2 }` или `{ it * 2 }` | > | `Function` | `(Int) -> Int` | > | `BiFunction` | `(A, B) -> C` | > | `Supplier` | `() -> T` | > | `Consumer` | `(T) -> Unit` | > | `Predicate` | `(T) -> Boolean` | > | `Runnable` | `() -> Unit` | > | `String::length` | `String::length` | > | нет неявного параметра | `it` | > | захват только effectively final | захват изменяемых переменных | > | лямбда только для SAM-интерфейса | функциональный тип первого класса | > > В Java лямбда - это реализация функционального интерфейса, поэтому для каждой формы нужен свой тип (`Function`, `BiFunction`, `Supplier`, `Consumer`, `Predicate`…). В Kotlin тип функции записывается напрямую: `(Int, String) -> Boolean` не требует придумывать имя. > > Захват изменяемых переменных - практическая разница. Java требует `final` или effectively final, вынуждая использовать массив-обёртку `int[] sum = {0}`. В Kotlin `var sum = 0` захватывается как есть (компилятор оборачивает его в `Ref`-объект). > > Сравнить: [Функции в Java](../../../Программирование%20на%20Java/Синтаксис%20и%20типы/Работа%20с%20данными%20и%20типами/Функции.md) > [!warning] Подводные камни > **`return` в лямбде запрещён** - он относился бы к внешней функции: > ```kotlin > fun find(items: List): Int? { > items.forEach { > if (it > 1) return it // выход из find, а не из лямбды! > } // работает только потому, что forEach inline > return null > } > > fun main() { println(find(listOf(1, 2, 3))) } // 2 > ``` > Это «нелокальный return», он разрешён только в inline-функциях. Для выхода из самой лямбды нужна метка: > ```kotlin > fun main() { > listOf(1, 2, 3).forEach { > if (it == 2) return@forEach // пропустить итерацию, как continue > println(it) // 1, затем 3 > } > } > ``` > > **`it` во вложенных лямбдах затеняется:** > ```kotlin > fun main() { > val nested = listOf(listOf(1, 2)) > // println(nested.map { it.map { it * 2 } }) // непонятно, какое it > println(nested.map { row -> row.map { v -> v * 2 } }) // [[2, 4]] > } > ``` > > **Каждая не-inline лямбда с захватом - объект.** В цикле на миллион итераций это миллион аллокаций. Стандартные операции (`map`, `filter`, `forEach`) объявлены `inline`, поэтому бесплатны; свои функции высшего порядка в горячем коде тоже стоит делать `inline`. > > **Лямбда, захватившая `this`, держит объект в памяти** - классическая утечка в Android, когда колбэк переживает активити. > > **Ссылка на перегруженную функцию неоднозначна:** > ```kotlin > fun show(x: Int) = "int" > fun show(x: String) = "str" > > fun main() { > // val f = ::show // ошибка: какая из двух? > val f: (Int) -> String = ::show // тип разрешает неоднозначность > println(f(1)) // int > } > ``` Дополнительно: [Функции высшего порядка](../../Функциональный%20Kotlin/Функции%20высшего%20порядка.md) Далее: [Деструктуризация](Деструктуризация.md)