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,218 @@
---
title: Деструктуризация
description: Разбор объекта на переменные, componentN, деструктуризация в лямбдах и циклах
tags:
- kotlin
- syntax
---
Деструктуризация - разбор объекта на отдельные переменные одной конструкцией.
```kotlin
data class Point(val x: Int, val y: Int)
fun main() {
val (x, y) = Point(1, 2)
println("$x $y") // 1 2
}
```
Компилятор превращает это в вызовы `component1()`, `component2()` и так далее:
```kotlin
data class Point(val x: Int, val y: Int)
fun main() {
val p = Point(1, 2)
val x = p.component1()
val y = p.component2()
println("$x $y") // 1 2
}
```
## Что можно деструктурировать
Любой тип, у которого есть функции `componentN()`. Автоматически они появляются у:
- [data class](../Базовые%20типы%20ООП/Data%20class.md) - по свойствам основного конструктора
- `Pair` и `Triple`
- `Map.Entry` - ключ и значение
- списков и массивов (первые 5 элементов)
```kotlin
fun main() {
val (a, b) = Pair(1, "x")
val (c, d, e) = Triple(1, 2, 3)
val (first, second) = listOf("a", "b", "c") // третий игнорируется
println("$a $b | $c $d $e | $first $second")
}
```
## Свой componentN
Для обычного класса операторы объявляются вручную:
```kotlin
class Range2(val from: Int, val to: Int) {
operator fun component1() = from
operator fun component2() = to
}
fun main() {
val (from, to) = Range2(1, 10)
println("$from..$to") // 1..10
}
```
Слово `operator` обязательно.
## Пропуск компонентов
```kotlin
data class User(val id: Int, val name: String, val email: String)
fun main() {
val (_, name, _) = User(1, "Иван", "i@x.ru")
println(name) // Иван
}
```
Подчёркивание означает «этот компонент не нужен» - `componentN()` для него даже не вызовется.
Если нужны только первые компоненты, лишние просто не перечисляют:
```kotlin
data class User(val id: Int, val name: String, val email: String)
fun main() {
val (id, name) = User(1, "Иван", "i@x.ru") // email не берём
println("$id $name") // 1 Иван
}
```
## В циклах
```kotlin
fun main() {
val map = mapOf("a" to 1, "b" to 2)
for ((key, value) in map) {
println("$key=$value") // a=1, затем b=2
}
val list = listOf("x", "y")
for ((index, item) in list.withIndex()) {
println("$index:$item") // 0:x, затем 1:y
}
}
```
Это самое частое применение деструктуризации.
## В лямбдах
```kotlin
fun main() {
val map = mapOf("a" to 1, "b" to 2)
map.forEach { (k, v) -> println("$k$v") } // a1 b2
println(map.map { (k, v) -> "$k=$v" }) // [a=1, b=2]
map.forEach { (_, v) -> print(v) } // 12
println()
val points = listOf(1 to 2, 3 to 4)
println(points.map { (x, y) -> x + y }) // [3, 7]
}
```
Параметр в скобках `{ (k, v) -> }` - это **один** параметр, который разбирается, а не два отдельных. Без скобок `{ k, v -> }` лямбда ожидала бы два аргумента, и код не скомпилировался бы.
## Возврат нескольких значений
Классическое применение - функция, которая должна вернуть пару значений:
```kotlin
data class Result2(val value: Int, val error: String?)
fun parse(s: String): Result2 =
s.toIntOrNull()?.let { Result2(it, null) } ?: Result2(0, "не число")
fun main() {
val (value, error) = parse("42")
println("$value $error") // 42 null
val (v2, e2) = parse("abc")
println("$v2 $e2") // 0 не число
}
```
`Pair` для этого тоже подходит, но свой `data class` лучше: имена компонентов осмысленны, а не `first`/`second`.
## Деструктуризация по именам (Kotlin 2.3+)
Классическая деструктуризация позиционная - порядок переменных должен совпадать с порядком свойств. Это хрупко: перестановка свойств в `data class` молча меняет смысл кода.
С Kotlin 2.3 разрабатывается деструктуризация по именам. Синтаксис меняется так:
- `val [a, b] = obj` - **квадратные** скобки, позиционно (как было)
- `val (name, email) = obj` - **круглые** скобки, теперь **по именам** свойств
```kotlin
data class User(val name: String, val email: String)
fun main() {
val user = User("ИМЯ", "ПОЧТА")
val (email, name) = user // по именам — порядок не важен
println("name=$name email=$email") // name=ИМЯ email=ПОЧТА
val [first, second] = user // позиционно
println("first=$first") // first=ИМЯ
}
```
Фича пока за флагом компилятора:
```bash
kotlinc -Xname-based-destructuring=complete App.kt
```
Значения флага: `only-syntax` (включить синтаксис `[]` и полную форму), `name-mismatch` (предупреждать, когда имена в позиционной деструктуризации не совпадают со свойствами), `complete` (включить короткую форму по именам).
> [!warning] Это ломающее изменение
> Круглые скобки меняют смысл: был порядок, станут имена. Код `val (email, name) = user` сегодня присвоит `email` первое свойство, а после включения фичи - свойство с именем `email`. Пока флаг не включён, вся деструктуризация в вашем коде позиционная.
> [!info] Отличия от Java
> В Java деструктуризации объектов нет. Ближайший аналог - record patterns из Java 21:
> ```java
> record Point(int x, int y) {}
> if (obj instanceof Point(int x, int y)) { ... }
> ```
> Но это работает только в `instanceof` и `switch`, а не при обычном объявлении переменных. Разбор `Map.Entry` в цикле в Java невозможен - нужно писать `e.getKey()` и `e.getValue()`.
>
> | Java | Kotlin |
> |---|---|
> | `var k = e.getKey(); var v = e.getValue();` | `val (k, v) = e` |
> | `for (var e : map.entrySet())` + геттеры | `for ((k, v) in map)` |
> | `p.x(); p.y();` | `val (x, y) = p` |
> | нет возврата нескольких значений | `Pair`, `Triple`, `data class` |
>
> Сравнить: [Record в Java](../../../Программирование%20на%20Java/Синтаксис%20и%20типы/Базовые%20типы%20ООП/Record.md)
> [!warning] Подводные камни
> **Деструктуризация позиционная, а не по именам.** Это главная опасность:
> ```kotlin
> data class User(val name: String, val email: String)
>
> fun main() {
> val (name, email) = User("Иван", "i@x.ru")
> println("$name / $email") // Иван / i@x.ru
> }
> ```
> Если позже в `data class` поменять порядок свойств на `(email, name)`, этот код **продолжит компилироваться**, но переменные получат чужие значения. Компилятор не поможет, потому что типы совпадают. Поэтому порядок свойств в `data class` - часть публичного API.
>
> **У обычного класса деструктуризации нет** - только у `data class` или при ручном объявлении `componentN()`.
>
> **В лямбде нужны скобки:**
> ```kotlin
> fun main() {
> val map = mapOf("a" to 1)
> map.forEach { (k, v) -> println("$k$v") } // верно: один параметр-пара
> map.forEach { e -> println("${e.key}${e.value}") } // тоже верно
> }
> ```
> Запись `{ k, v -> }` для `forEach` на `Map` не скомпилируется.
>
> **Список короче числа переменных - исключение в рантайме:**
> ```kotlin
> fun main() {
> val list = listOf(1)
> val (a, b) = list // компилируется!
> println("$a $b") // IndexOutOfBoundsException
> }
> ```
> У `List` есть `component1()`..`component5()`, и компилятор не знает фактическую длину. С `data class` такой проблемы нет.
>
> **`component1()` вызывается даже для неиспользуемой переменной** - если это не `_`. Для свойства с тяжёлым геттером лишняя работа.
>
> **Деструктуризация `Pair` теряет смысл имён.** `val (a, b) = getCoordinates()` не говорит, что где. Лучше `data class Coordinates(val lat: Double, val lon: Double)`.
Далее: [Приведение типов](Приведение%20типов.md)