В Kotlin деструктурирование выглядело так: val (name, age) = person. Но компилятор берет значения не по именам, а по позиции component1/component2.
Отсюда проблемы. Если поменяли порядок параметров в data class или сделали age вычисляемым свойством: то та же строка начинает доставать другое поле. Причем иногда код даже скомпилируется, но, конечно, смысл изменится: val (age, name) = person.
И вот теперь Kotlin эксперементально переводит круглые скобки на деструктурирование по имени. Синтаксис будет такой: (val name, val age) = person. И порядок внутри скобок не важен. Переименование явно: (val years = age, val theName = name) = person.
Позиционное же деструктурирование остается, но переезжает в квадратные скобки для Pair/Triple и коллекций: val [x, y] = point.
Разбираемся полностью.
TL;DR
-
Вводится новый синтаксис «
valвнутри круглых скобок», позволяющий выполнять деструктурирование по именам. Кроме того, вводится новый синтаксис с квадратными скобками для позиционного деструктурирования. -
Оба варианта сейчас находятся в статусе Experimental (включаются аргументом компилятора
-Xname-based-destructuring=only-syntax) и станут Stable в одном из будущих релизов. -
В отдалённой перспективе поведение синтаксиса «
valснаружи скобок» для деструктурирования изменится: вместо позиционного оно станет именным. -
Перед сменой поведения по умолчанию будет длительный период миграции, и инструменты для неё уже готовы.
-
Вы уже можете переключиться на новое поведение (
-Xname-based-destructuring=complete), но учитывайте его Experimental-статус. -
Компилятор поставляется с помощниками миграции, которые будут включены по умолчанию на протяжении нескольких версий, и пройдёт немало времени, прежде чем новое поведение станет стандартом.
-
Вы можете включить эти помощники уже сейчас, используя
-Xname-based-destructuring=name-mismatch.
Kotlin меняется: имена станут центральными в деструктурировании. В будущем val (name, age) = person будет извлекать свойства name и age из значения person, независимо от того, как и в каком порядке они были определены. Это означает переход от текущего подхода к деструктурированию, где на данный момент ключевым элементом является позиция. В этом посте объясняются причины изменений, стратегия миграции и то, как инструменты Kotlin помогают её выполнить.
Почему деструктурировать по именам, а не по позициям?
Деструктурирование чаще всего используют для доступа к свойствам data-классов. Например, мы можем определить класс Person так:
data class Person(val name: String, val age: Int)
Затем можно извлечь несколько основных свойств одним выражением. Это и называется деструктурированием значения на компоненты.
fun isValidPerson(p: Person) { val (name, age) = p return name.isNotEmpty() && age >= 0}
Сейчас деструктурирование выполняется по позициям. Переменные, которые мы вводим в деструктурирующем объявлении, часто повторяют имена свойств в data-классе, но язык этого не требует.
// это ровно та же функция, что и вышеfun isValidPerson(p: Person) { val (foo, bar) = p return foo.isNotEmpty() && bar >= 0}
Это отсутствие связи может приводить к проблемам: очень легко случайно поменять местами порядок двух свойств. Ошибка может проявиться позже из-за несовпадающих типов, но при этом будет находиться далеко от истинного источника.
То, как компоненты соотносятся с первичными свойствами, также мешает рефакторингу. Например, мы не можем сделать age вычисляемым свойством и при этом сохранить удобный синтаксис data-класса. Представьте, что мы вносим такое изменение:
data class Person(val name: String, val birthdate: Date) { val age = (Date.now() - birthdate).years}
Теперь каждое деструктурирование внезапно меняется с age на birthdate! Уточним: обеспечить совместимость на уровне исходного кода всё ещё возможно, но вам придётся сделать значительно больше работы.
Текущий подход к деструктурированию также противоречит абстракции. Если превратить Person в интерфейс, прежние случаи деструктурирования больше не будут работать. Можно обойти это, добавив собственные функции componentX, но обычно это считается продвинутой техникой. В результате большинство интерфейсов подобных возможностей не предоставляет.
interface Person { val name: String val age: Int operator fun component1(): String = name operator fun component2(): Int = age}
Эти проблемы исчезают, если деструктурирование зависит от имён, а не от позиций. Неважно, меняете ли вы порядок, превращаете вычисляемое свойство в первичное или наоборот, или определяете свойство в классе, интерфейсе или объекте. Имя свойства — стабильная характеристика, а значит, исходный код не требует изменений.
Новый синтаксис
Вы можете включить новый синтаксис, передав в компилятор аргумент -Xname-based-destructuring=only-syntax.
Без лишних слов рассмотрим новый синтаксис, который использует имена для деструктурирования. Вместо одного val снаружи скобок вы используете val для каждого свойства внутри круглых скобок.
fun isValidPerson(p: Person): Boolean (val name, val age) = p return name.isNotEmpty() && age >= 0}
Как и ожидается, порядок, в котором мы пишем val name и val age в примере выше, не имеет значения. Новый синтаксис также поддерживает переименование для случаев, когда новая переменная, которую вы хотите определить, не совпадает с именем свойства, к которому вы обращаетесь.
fun isValidPerson(p: Person): Boolean { (val age, val theName = name) = p return theName.isNotEmpty() && age >= 0}
Позиционное деструктурирование по-прежнему важно для некоторых сценариев. Например, у Pair и Triple на концептуальном уровне нет имён компонентов, и нет намерения заставлять код, использующий их, «засорять» именами вроде first и second. Позиционное деструктурирование также можно применять к коллекциям — и тогда доступных свойств просто нет. Новый синтаксис для позиционного деструктурирования использует квадратные скобки, отражая синтаксис будущих литералов коллекций. Вы можете выбирать, размещать ли val внутри скобок или снаружи.
fun isZero(point: Pair<Int, Int>): Boolean { val [x, y] = point // один вариант [val x, val y] = point // или другой return x == 0 && y == 0}
Весь этот новый синтаксис доступен везде, где можно выполнять деструктурирование, включая лямбда-выражения и циклы.
// рекомендуемый новый синтаксис для обхода mapfor ([key, value] in map) { // работаем с каждой записью}person?.let { (val name, val years = age) -> "$name is $years years old" }
Повторим: это полностью новый синтаксис. Начиная с версии 2.3.20 компилятор понимает, что он означает, и мы намерены сохранить его, когда функция достигнет статуса Stable.
Переосмысление круглых скобок
В какой-то момент в будущем мы планируем, что любое деструктурирование с круглыми скобками будет именным. Вы можете «попробовать это будущее» уже сейчас, используя аргумент компилятора -Xname-based-destructuring=complete.
Однако если у вас уже есть проект, переключение может сильно повлиять на код. Самая заметная проблема — если деструктурирование перестанет работать и код придётся обновлять. Ещё опаснее ситуация, когда деструктурирующие объявления остаются валидными, но меняют смысл.
Именно поэтому компилятор включает помощник миграции под аргументом -Xname-based-destructuring=name-mismatch. Когда он включён, компилятор выдаёт предупреждение в случаях, где поведение различается между позиционным и именным деструктурированием, или где код не будет принят, когда деструктурирование с круглыми скобками перестанет быть позиционным.
// принимается обоими вариантами с одинаковым поведением val (name, age) = person// предупреждение: принимается обоими, но поведение меняетсяval (age, name) = person// предупреждение: принимается только позиционным деструктурированиемval (personName, personAge) = person// IDE предлагает возможные исправления// - переименование: (val personName = name, val personAge = age) = person// - квадратные скобки: val [personName, personAge] = person
Будущее
Как уже отмечалось в этом посте, времени на миграцию к имённому деструктурированию будет достаточно. Наш текущий план выглядит так:
-
Начиная с версии 2.3.20 имённое деструктурирование находится в статусе Experimental, то есть для его использования нужен специальный аргумент компилятора.
-
Поддержка в IntelliJ IDEA может быть недостаточной, особенно в части миграции.
-
-
В версии 2.5.0 (в конце 2026 года) функция станет Stable.
-
Новый синтаксис будет доступен без дополнительной настройки.
-
Компилятор начнёт сообщать подсказки для миграции, а IntelliJ IDEA добавит инспекции и быстрые исправления, чтобы помочь в процессе.
-
Эта стадия примерно соответствует name-mismatch в аргументах компилятора, хотя мы можем внести некоторые изменения в отчётность в зависимости от отзывов пользователей.
-
-
К версии 2.7.0 (в конце 2027 года) деструктурирование с круглыми скобками станет именным.
-
Вы можете перейти к этой стадии раньше, используя complete в аргументах компилятора.
-
Это большое изменение, и мы не хотим торопиться. Если в любой момент в течение 2027 года станет ясно, что экосистема не готова, мы можем отложить изменение до следующей мажорной версии.
-
Мы ни в какой момент не планируем прекращать генерацию функций component для data-классов. Data-классы будут по-прежнему генерировать тот же байткод — имённое деструктурирование является возможностью на местах использования. Однако мы планируем ввести value-классы с несколькими полями без функций component. Это означает, что деструктурирование для value-классов будет только именным.
Ссылки
-
Release notes для Kotlin 2.3.20 — первой версии, предлагающей именное деструктурирование.
-
Документ дизайна (KEEP) для функции и соответствующее публичное обсуждение.

Присоединяйтесь к русскоязычному сообществу разработчиков на Spring Boot в телеграм — Spring АйО, чтобы быть в курсе последних новостей из мира разработки на Spring Boot и всего, что с ним связано.
ссылка на оригинал статьи https://habr.com/ru/articles/1035596/