9.8 KiB
title, description, tags
| title | description | tags | ||
|---|---|---|---|---|
| Деструктуризация | Разбор объекта на переменные, componentN, деструктуризация в лямбдах и циклах |
|
Деструктуризация - разбор объекта на отдельные переменные одной конструкцией.
data class Point(val x: Int, val y: Int)
fun main() {
val (x, y) = Point(1, 2)
println("$x $y") // 1 2
}
Компилятор превращает это в вызовы component1(), component2() и так далее:
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 - по свойствам основного конструктора
PairиTripleMap.Entry- ключ и значение- списков и массивов (первые 5 элементов)
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
Для обычного класса операторы объявляются вручную:
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 обязательно.
Пропуск компонентов
data class User(val id: Int, val name: String, val email: String)
fun main() {
val (_, name, _) = User(1, "Иван", "i@x.ru")
println(name) // Иван
}
Подчёркивание означает «этот компонент не нужен» - componentN() для него даже не вызовется.
Если нужны только первые компоненты, лишние просто не перечисляют:
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 Иван
}
В циклах
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
}
}
Это самое частое применение деструктуризации.
В лямбдах
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 -> } лямбда ожидала бы два аргумента, и код не скомпилировался бы.
Возврат нескольких значений
Классическое применение - функция, которая должна вернуть пару значений:
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- круглые скобки, теперь по именам свойств
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=ИМЯ
}
Фича пока за флагом компилятора:
kotlinc -Xname-based-destructuring=complete App.kt
Значения флага: only-syntax (включить синтаксис [] и полную форму), name-mismatch (предупреждать, когда имена в позиционной деструктуризации не совпадают со свойствами), complete (включить короткую форму по именам).
[!warning] Это ломающее изменение Круглые скобки меняют смысл: был порядок, станут имена. Код
val (email, name) = userсегодня присвоит
[!info] Отличия от Java В Java деструктуризации объектов нет. Ближайший аналог - record patterns из Java 21:
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) = efor (var e : map.entrySet())+ геттерыfor ((k, v) in map)p.x(); p.y();val (x, y) = pнет возврата нескольких значений Pair,Triple,data classСравнить: Record в Java
[!warning] Подводные камни Деструктуризация позиционная, а не по именам. Это главная опасность:
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().В лямбде нужны скобки:
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не скомпилируется.Список короче числа переменных - исключение в рантайме:
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).
Далее: Приведение типов