Мульти-декларации

Иногда удобно деструктуризировать объект на несколько переменных, например:

val (name, age) = person 

Этот синтаксис называется деструкторизирующее присваение. Он позволяет присвоить объект сразу нескольким переменным, разбив его на части. Мы объявили две переменные: name и age и теперь можем использовать их по отдельности.

println(name)
println(age)

Эта декларация транслируется в такой код:

val name = person.component1()
val age = person.component2()

Как и многое другое в Kotlin, мульти-декларации опираются на конвенцию: функции componentN() вызываются по имени, то есть могут быть объявлены как в классе person, так и вне его — в качестве расширений.

Заметьте, что функции componentN() нужно отмечать ключевым словом operator, чтобы позволить их использование в деструкторизирующем присваивании.

Деструкторизирующие присваивания также работают в циклах for:

for ((a, b) in collection) { ... }

В данном примере значения переменных a и b возращены методами component1() и component2(), вызванными неявно у элементов коллекции.

Например: возврат двух значений из функции

Предположим, нам нужно вернуть два значения из функции. Например, результат вычисления и какой-нибудь статус. Компактный способ достичь этого — объявление data-класса и возвращение его экземпляря:

data class Result(val result: Int, val status: Status)
fun function(...): Result {
    // вычисления
    
    return Result(result, status)
}

// Теперь мы можем использовать деструкторизирующее присваивание:
val (result, status) = function(...)

Так как data-классы автоматически объявляют componentN()-функции, мульти-декларации будут работать с ними "из коробки".

ПРИМЕЧАНИЕ: мы также могли использовать стандартный класс Pair, чтобы заствить функцию вернуть вернуть Pair<Int, Status>, но правильнее будет именовать ваши данные должным образом.

Пример: мульти-декларации и ассоциативные списки

Пожалуй, самый хороший способ итерации по ассоциативному списку:

for ((key, value) in map) {
   // do something with the key and the value
}

Чтобы это работало, мы должны:

  • представить ассоциативный список как последовательность значений, предоставив функцию iterator()
  • представить каждый элемент как пару с помощью функций component1() и component2()

И да, стандартная библиотека предоставляет такие расширения:

operator fun <K, V> Map<K, V>.iterator(): Iterator<Map.Entry<K, V>> = entrySet().iterator()
operator fun <K, V> Map.Entry<K, V>.component1() = getKey()
operator fun <K, V> Map.Entry<K, V>.component2() = getValue()

Так что вы можете свободно использовать мульти-декларации в циклах for с ассоциативными списками (так же как и с коллекциями экземпляров data-классов)