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

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

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-классов).

Подчеркивание для неиспользуемых переменных

Если вам не нужна переменная в объявлении деструктурирования, вы можете поместить символ подчеркивания вместо ее имени.

val (_, status) = getResult()

Функции оператора component() не вызываются для компонентов, которые пропускаются таким образом.

Деструктурирование в лямбдах

Вы можете использовать синтаксис объявлений деструктурирования для лямбда-параметров. Если у лямбды есть параметр типа Pair (или Map.Entry, или любого другого типа, который имеет соответствующие функции componentN), вы можете ввести несколько новых параметров вместо одного, заключив их в круглые скобки.

map.mapValues { entry -> "${entry.value}!" }
map.mapValues { (key, value) -> "$value!" }

Обратите внимание на разницу между объявлением двух параметров и объявлением пары деструктурирования вместо параметра.

{ a -> ... } // один параметр
{ a, b -> ... } // два параметра
{ (a, b) -> ... } // пара деструктурирования
{ (a, b), c -> ... } // пара деструктурирования и параметр

Если компонент деструктурированного параметра не используется, вы можете заменить его символом подчеркивания, чтобы избежать выдумывания его имени.

map.mapValues { (_, value) -> "$value!" }

Вы можете указать тип для всего деструктурированного параметра или отдельно для конкретного компонента.

map.mapValues { (_, value): Map.Entry<Int, String> -> "$value!" }

map.mapValues { (_, value: String) -> "$value!" }