Kotlin guide done. (20.10.26)

This commit is contained in:
2026-10-20 12:54:25 +03:00
parent da3ad9dd1a
commit 6853ada348
81 changed files with 30112 additions and 0 deletions
@@ -0,0 +1,273 @@
---
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<Int, Int>
```
Это значит аллокацию на каждую лямбду, если она захватывает переменные. Для функций высшего порядка в горячем коде существует `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<Integer,Integer>` | `(Int) -> Int` |
> | `BiFunction<A,B,C>` | `(A, B) -> C` |
> | `Supplier<T>` | `() -> T` |
> | `Consumer<T>` | `(T) -> Unit` |
> | `Predicate<T>` | `(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>): 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)