--- 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)