Что нового в Kotlin 1.7.20
Вышел Kotlin 1.7.20! Вот некоторые основные нововведения этого выпуска:
Новый менеджер памяти Kotlin/Native теперь включён по умолчанию
Мы представили новую экспериментальную функцию для JVM: inline-классы с обобщённым базовым типом
Краткий обзор изменений также представлен в этом видео:
Поддержка плагинов компилятора Kotlin K2
Команда Kotlin продолжает стабилизировать компилятор K2. K2 всё ещё находится в состоянии Alpha (об этом было объявлено в выпуске Kotlin 1.7.0), но теперь он поддерживает несколько плагинов компилятора. Чтобы следить за новостями команды Kotlin о новом компиляторе, можно подписаться на эту задачу в YouTrack.
Начиная с выпуска 1.7.20, компилятор Kotlin K2 поддерживает следующие плагины:
AtomicFU
jvm-abi-gen
Подробнее о новом компиляторе и его преимуществах можно узнать из следующих видео:
Как включить компилятор Kotlin K2
Чтобы включить компилятор Kotlin K2 и протестировать его, используйте следующий параметр компилятора:
-Xuse-k2
Его можно указать в файле build.gradle(.kts):
tasks.withType<KotlinCompile> {
kotlinOptions.useK2 = true
}
compileKotlin {
kotlinOptions.useK2 = true
}
Оцените прирост производительности в своих проектах JVM и сравните его с результатами старого компилятора.
Оставьте отзыв о новом компиляторе K2
Мы будем рады любым вашим отзывам:
Оставьте отзыв напрямую разработчикам K2 в Kotlin Slack: получите приглашение и присоединитесь к каналу #k2-early-adopters.
Сообщите о любых проблемах, возникших при работе с новым компилятором K2, в наш трекер задач.
Включите параметр Отправлять статистику использования, чтобы разрешить JetBrains собирать анонимные данные об использовании K2.
Язык
В Kotlin 1.7.20 представлены предварительные версии новых языковых функций, а также введены ограничения на вывод типов в builder-функциях:
Предварительная версия оператора ..< для создания диапазонов без включённой верхней границы
В этом выпуске представлен новый оператор ..<. В Kotlin есть оператор .. для задания диапазона значений. Новый оператор ..< работает как функция until и помогает определить диапазон без включённой верхней границы.
Наши исследования показывают, что этот новый оператор лучше подходит для задания диапазонов без включённой верхней границы и ясно показывает, что верхняя граница не включается в диапазон.
Вот пример использования оператора ..< в выражении when:
when (value) {
in 0.0..<0.25 -> // First quarter
in 0.25..<0.5 -> // Second quarter
in 0.5..<0.75 -> // Third quarter
in 0.75..1.0 -> // Last quarter <- Note closed range here
}
Изменения API стандартной библиотеки
В пакеты kotlin.ranges общей стандартной библиотеки Kotlin будут добавлены следующие типы и операции:
Новый интерфейс OpenEndRange<T>
Новый интерфейс для представления диапазонов без включённой верхней границы очень похож на существующий интерфейс ClosedRange<T>:
interface OpenEndRange<T : Comparable<T>> {
// Lower bound
val start: T
// Upper bound, not included in the range
val endExclusive: T
operator fun contains(value: T): Boolean = value >= start && value < endExclusive
fun isEmpty(): Boolean = start >= endExclusive
}
Реализация OpenEndRange для существующих итерируемых диапазонов
Когда разработчикам требуется диапазон с исключённой верхней границей, сейчас они используют функцию until, фактически создавая замкнутый итерируемый диапазон с теми же значениями. Чтобы такие диапазоны можно было использовать в новом API, принимающем OpenEndRange<T>, мы хотим реализовать этот интерфейс в существующих итерируемых диапазонах: IntRange, LongRange, CharRange, UIntRange и ULongRange. Таким образом, они будут одновременно реализовывать интерфейсы ClosedRange<T> и OpenEndRange<T>.
class IntRange : IntProgression(...), ClosedRange<Int>, OpenEndRange<Int> {
override val start: Int
override val endInclusive: Int
override val endExclusive: Int
}
Операторы rangeUntil для стандартных типов
Операторы rangeUntil будут предоставлены для тех же типов и сочетаний, что и оператор rangeTo. Для прототипирования мы предоставляем их в виде функций-расширений, но для согласованности планируем сделать их членами типов до стабилизации API диапазонов без включённой верхней границы.
Как включить оператор ..<
Чтобы использовать оператор ..< или реализовать соглашение об этом операторе для собственных типов, включите параметр компилятора -language-version 1.8.
Для использования новых элементов API, добавленных для поддержки диапазонов стандартных типов без включённой верхней границы, требуется явное согласие, как обычно для экспериментального API стандартной библиотеки: @OptIn(ExperimentalStdlibApi::class). Кроме того, можно использовать параметр компилятора -opt-in=kotlin.ExperimentalStdlibApi.
Улучшенное строковое представление синглтонов и иерархий запечатанных классов с объектами-данными
В этом выпуске представлен новый тип объявления object: data object. Объект-данные концептуально ведёт себя так же, как обычное объявление object, но изначально имеет понятное представление toString.
package org.example
object MyObject
data object MyDataObject
fun main() {
println(MyObject) // org.example.MyObject@1f32e575
println(MyDataObject) // MyDataObject
}
Благодаря этому объявления data object идеально подходят для иерархий запечатанных классов, где их можно использовать наряду с объявлениями data class. В этом фрагменте объявление EndOfFile в виде data object вместо обычного object означает, что оно получит красивое toString без необходимости переопределять его вручную, сохраняя симметрию с сопутствующими определениями data class:
sealed class ReadResult {
data class Number(val value: Int) : ReadResult()
data class Text(val value: String) : ReadResult()
data object EndOfFile : ReadResult()
}
fun main() {
println(ReadResult.Number(1)) // Number(value=1)
println(ReadResult.Text("Foo")) // Text(value=Foo)
println(ReadResult.EndOfFile) // EndOfFile
}
Как включить объекты-данные
Чтобы использовать объявления объектов-данных в коде, включите параметр компилятора -language-version 1.9. В проекте Gradle для этого добавьте следующее в файл build.gradle(.kts):
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile>().configureEach {
// ...
kotlinOptions.languageVersion = "1.9"
}
compileKotlin {
// ...
kotlinOptions.languageVersion = '1.9'
}
Подробнее об объектах-данных и оставьте отзыв об их реализации в соответствующем документе KEEP.
Новые ограничения на вывод типов в builder-функциях
В Kotlin 1.7.20 введены существенные ограничения на использование вывода типов в builder-функциях, которые могут повлиять на ваш код. Эти ограничения распространяются на код с лямбда-функциями builder, где невозможно вывести тип параметра без анализа самой лямбды. Параметр используется в качестве аргумента. Теперь компилятор всегда будет выдавать ошибку для такого кода и предлагать указать тип явно.
Это несовместимое изменение, однако наши исследования показывают, что такие случаи встречаются очень редко, поэтому ограничения не должны повлиять на ваш код. Если всё же повлияют, рассмотрите следующие случаи:
-
Вывод типов в builder-функциях с расширением, скрывающим члены.
Если в коде есть функция-расширение с тем же именем, которая будет использоваться при выводе типов в builder-функции, компилятор выдаст ошибку:
class Data { fun doSmth() {} // 1 } fun <T> T.doSmth() {} // 2 fun test() { buildList { this.add(Data()) this.get(0).doSmth() // Resolves to 2 and leads to error } }Чтобы исправить код, необходимо явно указать тип:
class Data { fun doSmth() {} // 1 } fun <T> T.doSmth() {} // 2 fun test() { buildList<Data> { // Type argument! this.add(Data()) this.get(0).doSmth() // Resolves to 1 } } -
Вывод типов в builder-функциях с несколькими лямбдами, когда аргументы типа не указаны явно.
Если при выводе типов в builder-функции используются два или более блока лямбда-выражений, они влияют на тип. Чтобы избежать ошибки, компилятор требует указать тип:
fun <T: Any> buildList( first: MutableList<T>.() -> Unit, second: MutableList<T>.() -> Unit ): List<T> { val list = mutableListOf<T>() list.first() list.second() return list } fun main() { buildList( first = { // this: MutableList<String> add("") }, second = { // this: MutableList<Int> val i: Int = get(0) println(i) } ) }Чтобы исправить ошибку, необходимо явно указать тип и устранить несовпадение типов:
fun main() { buildList<Int>( first = { // this: MutableList<Int> add(0) }, second = { // this: MutableList<Int> val i: Int = get(0) println(i) } ) }
Если ваш случай не упомянут выше, сообщите о проблеме нашей команде.
Подробнее об этом обновлении вывода типов в builder-функциях см. в задаче YouTrack.
Kotlin/JVM
В Kotlin 1.7.20 появились обобщённые inline-классы, добавлены дополнительные оптимизации байт-кода для делегированных свойств и поддержка IR в задаче генерации заглушек kapt, что позволяет использовать с kapt все новейшие возможности Kotlin:
Обобщённые inline-классы
В Kotlin 1.7.20 базовый тип inline-классов JVM может быть параметром типа. Компилятор отображает его в Any? или, в общем случае, в верхнюю границу параметра типа.
Рассмотрим следующий пример:
@JvmInline
value class UserId<T>(val value: T)
fun compute(s: UserId<String>) {} // Compiler generates fun compute-<hashcode>(s: Any?)
Функция принимает inline-класс в качестве параметра. Параметр отображается в верхнюю границу, а не в аргумент типа.
Чтобы включить эту функцию, используйте параметр компилятора -language-version 1.8.
Будем рады вашим отзывам об этой функции в YouTrack.
Дополнительные варианты оптимизации делегированных свойств
В Kotlin 1.6.0 мы оптимизировали делегирование свойству, исключив поле $delegate и обеспечив непосредственный доступ к свойству, на которое ссылаются. В версии 1.7.20 эта оптимизация реализована для дополнительных случаев. Поле $delegate теперь будет исключаться, если делегатом является:
-
Именованный объект:
object NamedObject { operator fun getValue(thisRef: Any?, property: KProperty<*>): String = ... } val s: String by NamedObject -
Свойство
valс модификатором final, имеющее поле для хранения и стандартный геттер в том же модуле:val impl: ReadOnlyProperty<Any?, String> = ... class A { val s: String by impl } -
Константное выражение, элемент enum,
thisилиnull. Вот примерthis:class A { operator fun getValue(thisRef: Any?, property: KProperty<*>) ... val s by this }
Подробнее о делегированных свойствах.
Будем рады вашим отзывам об этой функции в YouTrack.
Поддержка бэкенда JVM IR в задаче генерации заглушек kapt
До версии 1.7.20 задача генерации заглушек kapt использовала старый бэкенд, а повторяемые аннотации не работали с kapt. В Kotlin 1.7.20 мы добавили поддержку бэкенда JVM IR в задаче генерации заглушек kapt. Это позволяет использовать с kapt все новейшие возможности Kotlin, в том числе повторяемые аннотации.
Чтобы использовать бэкенд IR в kapt, добавьте следующий параметр в файл gradle.properties:
kapt.use.jvm.ir=true
Будем рады вашим отзывам об этой функции в YouTrack.
Kotlin/Native
В Kotlin 1.7.20 новый менеджер памяти Kotlin/Native включён по умолчанию, а также добавлена возможность настраивать файл Info.plist:
Новый менеджер памяти Kotlin/Native включён по умолчанию
В этом выпуске дальнейшие улучшения стабильности и производительности нового менеджера памяти позволяют повысить его статус до Beta.
Предыдущий менеджер памяти усложнял написание параллельного и асинхронного кода, в том числе создание библиотеки kotlinx.coroutines. Это мешало внедрению Kotlin Multiplatform Mobile, поскольку ограничения параллельного выполнения создавали проблемы при совместном использовании кода Kotlin на платформах iOS и Android. Новый менеджер памяти наконец открывает путь к переводу Kotlin Multiplatform Mobile в статус Beta.
Новый менеджер памяти также поддерживает кэш компилятора, благодаря чему время компиляции сопоставимо с предыдущими выпусками. Подробнее о преимуществах нового менеджера памяти можно узнать в нашей первоначальной статье в блоге о предварительной версии. Дополнительные технические сведения доступны в документации.
Настройка и конфигурация
Начиная с Kotlin 1.7.20, новый менеджер памяти используется по умолчанию. Дополнительная настройка практически не требуется.
Если вы уже включили его вручную, можно удалить параметр kotlin.native.binary.memoryModel=experimental из gradle.properties или binaryOptions["memoryModel"] = "experimental" из файла build.gradle(.kts).
При необходимости можно вернуться к устаревшему менеджеру памяти с помощью параметра kotlin.native.binary.memoryModel=strict в gradle.properties. Однако поддержка кэша компилятора для устаревшего менеджера памяти больше недоступна, поэтому время компиляции может увеличиться.
Замораживание
В новом менеджере памяти замораживание объявлено устаревшим. Не используйте его, если только вашему коду не требуется работать с устаревшим менеджером (где замораживание по-прежнему необходимо). Это может быть полезно авторам библиотек, которым нужно сохранять поддержку устаревшего менеджера памяти, или разработчикам, которым требуется запасной вариант на случай проблем с новым менеджером памяти.
В таких случаях можно временно поддерживать код для обоих менеджеров памяти — нового и устаревшего. Чтобы игнорировать предупреждения об устаревании, выполните одно из следующих действий:
Добавьте аннотацию
@OptIn(FreezingIsDeprecated::class)к использованию устаревшего API.Примените
languageSettings.optIn("kotlin.native.FreezingIsDeprecated")ко всем наборам исходного кода Kotlin в Gradle.Передайте флаг компилятора
-opt-in=kotlin.native.FreezingIsDeprecated.
Вызов приостанавливающих функций Kotlin из Swift/Objective-C
Новый менеджер памяти по-прежнему ограничивает вызов функций Kotlin suspend из Swift и Objective-C потоками, отличными от главного, однако это ограничение можно снять с помощью нового параметра Gradle.
Это ограничение было введено в устаревшем менеджере памяти из-за случаев, когда код отправлял продолжение для возобновления в исходном потоке. Если в этом потоке отсутствовал поддерживаемый цикл обработки событий, задача никогда бы не выполнилась, а сопрограмма не была бы возобновлена.
В некоторых случаях это ограничение больше не требуется, но проверить все необходимые условия непросто. Поэтому мы решили сохранить его в новом менеджере памяти, добавив параметр, который позволяет отключить ограничение. Для этого добавьте следующий параметр в gradle.properties:
kotlin.native.binary.objcExportSuspendFunctionLaunchThreadRestriction=none
Команда Kotlin выражает огромную благодарность Ахмеду Эль-Хелву за реализацию этого параметра.
Оставьте отзыв
Это существенное изменение для нашей экосистемы. Будем рады вашим отзывам — они помогут сделать её ещё лучше.
Попробуйте новый менеджер памяти в своих проектах и оставьте отзыв в нашем трекере задач YouTrack.
Настройка файла Info.plist
При создании фреймворка компилятор Kotlin/Native генерирует файл списка свойств информации Info.plist. Ранее настроить его содержимое было непросто. В Kotlin 1.7.20 можно напрямую задавать следующие свойства:
Свойство |
Двоичный параметр |
|---|---|
|
|
|
|
|
|
Для этого используйте соответствующий двоичный параметр. Передайте флаг компилятора -Xbinary=$option=$value или задайте DSL Gradle binaryOption(option, value) для нужного фреймворка.
Команда Kotlin выражает огромную благодарность Мадсу Агеру за реализацию этой функции.
Kotlin/JS
В Kotlin/JS появились улучшения, повышающие удобство разработки и производительность:
Генерация Klib выполняется быстрее как при инкрементальной сборке, так и при чистой сборке благодаря более эффективной загрузке зависимостей.
Инкрементальная компиляция для бинарных файлов разработки была переработана, что значительно улучшило сценарии чистой сборки, ускорило инкрементальные сборки и повысило стабильность.
Мы улучшили генерацию
.d.tsдля вложенных объектов, запечатанных классов и параметров конструкторов со значениями по умолчанию.
Gradle
Обновления плагина Kotlin Gradle направлены на обеспечение совместимости с новыми функциями Gradle и последними версиями Gradle.
В Kotlin 1.7.20 появились изменения для поддержки Gradle 7.1. Устаревшие методы и свойства были удалены или заменены, что сократило количество предупреждений об устаревании, выдаваемых плагином Kotlin Gradle, и позволило продолжить работу над поддержкой Gradle 8.0.
Однако есть несколько потенциально несовместимых изменений, на которые стоит обратить внимание:
Настройка целевых платформ
org.jetbrains.kotlin.gradle.dsl.SingleTargetExtensionтеперь имеет параметр типа,SingleTargetExtension<T : KotlinTarget>.Соглашение
kotlin.targets.fromPreset()объявлено устаревшим. Вместо него можно по-прежнему использоватьkotlin.targets { fromPreset() }, но мы рекомендуем настраивать целевые платформы явно.-
Автоматически сгенерированные Gradle аксессоры для целевых платформ больше недоступны внутри блока
kotlin.targets { }. Вместо этого используйте методfindByName("targetName").Обратите внимание, что такие аксессоры по-прежнему доступны в случае
kotlin.targets, напримерkotlin.targets.linuxX64.
Настройка каталогов исходного кода
Теперь плагин Kotlin Gradle добавляет Kotlin SourceDirectorySet в качестве расширения kotlin к группе SourceSet в Java. Это позволяет настраивать каталоги исходного кода в файле build.gradle.kts так же, как они настраиваются в Java, Groovy и Scala:
sourceSets {
main {
kotlin {
java.setSrcDirs(listOf("src/java"))
kotlin.setSrcDirs(listOf("src/kotlin"))
}
}
}
Теперь вам не нужно использовать устаревшее соглашение Gradle и указывать каталоги исходного кода для Kotlin.
Помните, что для доступа к KotlinSourceSet можно также использовать расширение kotlin:
kotlin {
sourceSets {
main {
// ...
}
}
}
Новый метод настройки цепочки инструментов JVM
В этом выпуске появился новый метод jvmToolchain() для включения функции цепочек инструментов JVM. Если вам не нужны дополнительные поля конфигурации, такие как implementation или vendor, можно использовать этот метод из расширения Kotlin:
kotlin {
jvmToolchain(17)
}
Это упрощает настройку проекта Kotlin без дополнительной конфигурации. До этого выпуска версию JDK можно было указать только следующим образом:
kotlin {
jvmToolchain {
languageVersion.set(JavaLanguageVersion.of(17))
}
}
Стандартная библиотека
В Kotlin 1.7.20 появились новые функции-расширения для класса java.nio.file.Path, позволяющие обходить дерево файлов:
walk()лениво обходит дерево файлов, корнем которого является указанный путь.fileVisitor()позволяет отдельно создатьFileVisitor.FileVisitorопределяет действия над каталогами и файлами во время их обхода.visitFileTree(fileVisitor: FileVisitor, ...)принимает готовыйFileVisitorи использует под капотомjava.nio.file.Files.walkFileTree().visitFileTree(..., builderAction: FileVisitorBuilder.() -> Unit)создаетFileVisitorс помощьюbuilderActionи вызывает функциюvisitFileTree(fileVisitor, ...).FileVisitResult, тип возвращаемого значенияFileVisitor, имеет значение по умолчаниюCONTINUE, которое продолжает обработку файла.
Вот несколько примеров использования новых функций-расширений:
-
Явно создайте
FileVisitor, а затем используйте его:val cleanVisitor = fileVisitor { onPreVisitDirectory { directory, attributes -> // Some logic on visiting directories FileVisitResult.CONTINUE } onVisitFile { file, attributes -> // Some logic on visiting files FileVisitResult.CONTINUE } } // Some logic may go here projectDirectory.visitFileTree(cleanVisitor) -
Создайте
FileVisitorс помощьюbuilderActionи сразу же используйте его:projectDirectory.visitFileTree { // Definition of the builderAction: onPreVisitDirectory { directory, attributes -> // Some logic on visiting directories FileVisitResult.CONTINUE } onVisitFile { file, attributes -> // Some logic on visiting files FileVisitResult.CONTINUE } } -
Обойдите дерево файлов, корнем которого является указанный путь, с помощью функции
walk():@OptIn(kotlin.io.path.ExperimentalPathApi::class) fun traverseFileTree() { val cleanVisitor = fileVisitor { onPreVisitDirectory { directory, _ -> if (directory.name == "build") { directory.toFile().deleteRecursively() FileVisitResult.SKIP_SUBTREE } else { FileVisitResult.CONTINUE } } onVisitFile { file, _ -> if (file.extension == "class") { file.deleteExisting() } FileVisitResult.CONTINUE } } val rootDirectory = createTempDirectory("Project") rootDirectory.resolve("src").let { srcDirectory -> srcDirectory.createDirectory() srcDirectory.resolve("A.kt").createFile() srcDirectory.resolve("A.class").createFile() } rootDirectory.resolve("build").let { buildDirectory -> buildDirectory.createDirectory() buildDirectory.resolve("Project.jar").createFile() } // Use walk function: val directoryStructure = rootDirectory.walk(PathWalkOption.INCLUDE_DIRECTORIES) .map { it.relativeTo(rootDirectory).toString() } .toList().sorted() assertPrints(directoryStructure, "[, build, build/Project.jar, src, src/A.class, src/A.kt]") rootDirectory.visitFileTree(cleanVisitor) val directoryStructureAfterClean = rootDirectory.walk(PathWalkOption.INCLUDE_DIRECTORIES) .map { it.relativeTo(rootDirectory).toString() } .toList().sorted() assertPrints(directoryStructureAfterClean, "[, src, src/A.kt]") //sampleEnd }
Как и для любого экспериментального API, для использования новых расширений требуется явное согласие: @OptIn(kotlin.io.path.ExperimentalPathApi::class) или @kotlin.io.path.ExperimentalPathApi. Также можно использовать параметр компилятора: -opt-in=kotlin.io.path.ExperimentalPathApi.
Мы будем признательны за ваши отзывы о функции walk() и функциях-расширениях visit в YouTrack.
Обновления документации
С момента предыдущего выпуска в документации Kotlin произошли следующие заметные изменения:
Обновленные и улучшенные страницы
Обзор основных типов — узнайте об основных типах, используемых в Kotlin: числах, логических значениях, символах, строках, массивах и беззнаковых целых числах.
Среды разработки для Kotlin — ознакомьтесь со списком сред разработки с официальной поддержкой Kotlin и инструментов с плагинами, поддерживаемыми сообществом.
Новые статьи в журнале Kotlin Multiplatform
Разработка нативных и кроссплатформенных приложений: что выбрать? — ознакомьтесь с нашим обзором кроссплатформенной разработки приложений и нативного подхода, а также их преимуществ.
Шесть лучших фреймворков для кроссплатформенной разработки приложений — узнайте об основных аспектах, которые помогут выбрать подходящий фреймворк для вашего кроссплатформенного проекта.
Новые и обновленные руководства
Начало работы с Kotlin Multiplatform — узнайте о кроссплатформенной мобильной разработке на Kotlin и создайте приложение, работающее на Android и iOS.
Создание веб-приложения с помощью React и Kotlin/JS — создайте браузерное приложение, изучая DSL Kotlin и возможности типичной программы на React.
Изменения в документации к выпускам
Мы больше не публикуем список рекомендуемых библиотек kotlinx для каждого выпуска. В этом списке указывались только версии, рекомендованные и проверенные вместе с Kotlin. При этом не учитывалось, что некоторые библиотеки зависят друг от друга и требуют определенной версии kotlinx, которая может отличаться от рекомендуемой версии Kotlin.
Мы ищем способ предоставлять информацию о взаимосвязях и зависимостях библиотек, чтобы при обновлении версии Kotlin в проекте было понятно, какую версию библиотеки kotlinx следует использовать.
Установка Kotlin 1.7.20
IntelliJ IDEA 2021.3, 2022.1 и 2022.2 автоматически предлагают обновить плагин Kotlin до версии 1.7.20.
Новый компилятор командной строки можно скачать на странице выпуска на GitHub.
Руководство по совместимости с Kotlin 1.7.20
Хотя Kotlin 1.7.20 — промежуточный выпуск, нам все же пришлось внести несовместимые изменения, чтобы ограничить распространение проблем, появившихся в Kotlin 1.7.0.
Подробный список таких изменений см. в руководстве по совместимости с Kotlin 1.7.20.
© 2010–2026 JetBrains s.r.o. and Kotlin Programming Language contributors
Licensed under the Apache License, Version 2.0.
https://kotlinlang.org/docs/whatsnew1720.html