Мульти-декларации
Иногда удобно деструктуризировать объект на несколько переменных, например:
val (name, age) = person
Этот синтаксис называется деструктуризирующим присваиванием. Он позволяет присвоить объект сразу нескольким переменным,
разбив его на части. Вы объявили две переменные: name и age, и теперь можете использовать их по отдельности.
println(name)
println(age)
Эта декларация транслируется в такой код:
val name = person.component1()
val age = person.component2()
Функции component1() и component2() — это ещё один пример принципа конвенций, широко используемого в Kotlin
(например, для операторов + и * или циклов for). В правой части деструктурирующей декларации может находиться
что угодно, если для этого объекта можно вызвать нужное количество функций componentN(). Конечно, это могут быть
component3(), component4() и так далее.
Функции
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()
Операторные функции componentN() не вызываются для компонентов, которые пропускаются таким образом.
Деструктурирование в лямбдах
Вы можете использовать синтаксис объявлений деструктурирования для лямбда-параметров. Если у лямбды есть параметр типа
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!" }
Деструктурирование по именам
Kotlin поддерживает деструктурирующие декларации по именам, в которых переменные сопоставляются со свойствами по имени,
а не по позиции, заданной функциями componentN() при позиционном деструктурировании.
Подробнее о деструктурировании по именам см. в KEEP этой возможности.
При позиционном деструктурировании переменные соответствуют порядку функций componentN(), например:
data class User(val username: String, val email: String)
fun main() {
val user = User("alice", "[email protected]")
val (email, username) = user
println(email)
// alice
println(username)
// [email protected]
}
В этом примере деструктурирование опирается на порядок функций componentN(), поэтому email получает значение
username, а username получает значение email.
При деструктурировании по именам извлекаемые значения определяются именами свойств, а не позициями функций
componentN():
fun main() {
val user = User("alice", "[email protected]")
// Используется явная форма деструктурирования по именам
(val mail = email, val name = username) = user
println(name)
// alice
println(mail)
// [email protected]
}
Деструктурирование по именам — экспериментальная возможность.
Когда вы включаете её, появляется и новый синтаксис для позиционного деструктурирования с использованием квадратных
скобок. Используйте этот синтаксис для типов, где важен порядок элементов, например для списков и других упорядоченных
коллекций, а также для неименованных кортежей вроде Pair или Triple:
val point = Pair(10, 20)
// Используется позиционное деструктурирование
val [x, y] = point
Вы можете управлять тем, как компилятор интерпретирует деструктурирующие декларации, с помощью параметра компилятора
-Xname-based-destructuring.
У него есть следующие режимы:
only-syntaxвключает явную форму деструктурирования по именам, не меняя поведение существующих деструктурирующих деклараций.name-mismatchсообщает предупреждения, когда при позиционном деструктурированииdata-классов используются имена переменных, не совпадающие с именами свойств.completeвключает короткую форму деструктурирования по именам с круглыми скобками и продолжает поддерживать позиционное деструктурирование с синтаксисом квадратных скобок.
Прежде чем включать режим
complete, просмотрите и исправьте предупреждения, полученные в режимеname-mismatch. Эти предупреждения показывают, какие деструктурирующие декларации компилятор будет интерпретировать иначе в режимеcomplete, и содержат предложения по их переписыванию.
Если вы используете режим complete, короткий синтаксис деструктурирования с круглыми скобками сопоставляет переменные
с именами свойств, а не опирается на позицию:
val (email, username) = user
Чтобы включить деструктурирование по именам в проекте, добавьте параметр компилятора в файл конфигурации сборки:
Gradle
kotlin {
compilerOptions {
freeCompilerArgs.add("-Xname-based-destructuring=only-syntax")
}
}
Maven
<build>
<plugins>
<plugin>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-plugin</artifactId>
<configuration>
<args>
<arg>-Xname-based-destructuring=only-syntax</arg>
</args>
</configuration>
</plugin>
</plugins>
</build>