Доброго дня! Сегодня поговорим об одной из экспериментальных фич Kotlin, а именно о деструктурировании.
Проблема
Деструктурирование в Kotlin - удобная вещь. Всё работает через сгенерированные методы componentN(), и синтаксис получается приятным:
data class User(val name: String, val age: Int)
val user = User("Max", 23)
val (name, age) = user
println("$name is $age years old")
Но за этим удобством скрывается ловушка. Деструктурирование позиционное - значит, name получает значение component1(), age - component2(). И если кто-то решит переставить поля местами или добавить новое поле в начало класса:
data class User(val id: Long, val name: String, val age: Int)
Компилятор не выдаст ошибку, код соберётся - просто name теперь будет содержать id, а age - name. Ещё хуже ситуация с интерфейсами: деструктурировать объект через интерфейс нельзя, потому что componentN() объявляется только в data class.
В Kotlin 2.3.20 появилась экспериментальная поддержка именованного деструктурирования. Идея простая: вместо позиций используем имена свойств, а синтаксис разделяется на два вида.
Именованное деструктурирование - круглые скобки:
val (val name, val age) = user
Теперь name привязывается к свойству name, а не к позиции. Переставляйте поля
как угодно - код не сломается.
Если нужно переименовать переменную на месте:
val (val years = age, val theName = name) = user
println("$theName is $years years old")
Позиционное деструктурирование - квадратные скобки:
val [x, y] = point
Таким образом разработчики чётко разделили намерение: если хочешь по именам, то используй (), но если хочешь по позициям - то будь добр, используй [].
Деструктурирование через интерфейс
Это пожалуй самое интересное. Теперь можно деструктурировать через интерфейс, если свойства в нём объявлены:
interface Named {
val name: String
val age: Int
}
data class User(override val name: String, override val age: Int) : Named
fun printInfo(entity: Named) {
val (val name, val age) = entity
println("$name is $age")
}
Раньше такой код просто не компилировался. Теперь компилятор ищет свойство по имени и находит его в интерфейсе.
Поддержка экспериментальная, поэтому включается флагом компилятора:
-Xname-based-destructuring=only-syntax
Для Gradle:
kotlin {
compilerOptions {
freeCompilerArgs.add("-Xname-based-destructuring=only-syntax")
}
}
Итог
Именованное деструктурирование - небольшое, но важное изменение. Оно убирает целый класс тихих ошибок, которые легко допустить при рефакторинге, и открывает деструктурирование для интерфейсов. Синтаксис с () и [] хорошо передаёт намерение.
Если вы активно используете деструктурирование в data-классах - стоит уже сейчас попробовать новый синтаксис и пробежаться по коду.