Мульти-декларации
Иногда удобно деструктуризировать объект на несколько переменных, например:
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!" }