[Перевод] Что нового в Kotlin 2.1.20-RC3
В этой статье мы разберём некоторые важные улучшения релиза Early Access Preview (EAP). Полный список изменений можно найти в журнале изменений на GitHub.
Итак, рассмотрим некоторые детали релиза:
Компилятор Kotlin K2: новый плагин kapt по умолчанию.
Kotlin Multiplatform: новый DSL для замены плагина Gradle Application.
Kotlin/Native: новая фаза оптимизации инлайнинга.
Kotlin/Wasm: пользовательские форматтеры, включенные по умолчанию, и переход на Provider API.
Gradle: поддержка Gradle 8.11, совместимость с Isolated Projects и пользовательские варианты публикации.
Стандартная библиотека: универсальные атомарные типы, улучшенная поддержка UUID и новая функциональность для отслеживания времени.
Компилятор Compose: информация об исходных файлах включена по умолчанию.
Поддержка IDE
Плагины Kotlin, поддерживающие версию 2.1.20-RC3, уже включены в последние версии IntelliJ IDEA и Android Studio. Вам не нужно обновлять плагин Kotlin в IDE. Нужно только изменить версию Kotlin на 2.1.20-RC3 в скриптах сборки.
Подробнее о том, как обновиться до новой версии, смотрите в этом разделе.
Компилятор Kotlin K2: плагин kapt теперь по умолчанию
Начиная с Kotlin 2.1.20-RC3, реализация плагина kapt для K2 компилятора включена по умолчанию для всех проектов.
Команда JetBrains выпустила новую реализацию плагина kapt с K2 компилятором ещё в Kotlin 1.9.20. С тех пор мы продолжили развивать внутреннюю реализацию K2 kapt и сделали его поведение похожим на версию K1, значительно улучшив при этом производительность.
Если при использовании kapt с K2 компилятором возникают проблемы, можно временно вернуться к предыдущей реализации плагина.
Для этого добавьте следующую опцию в файл gradle.properties проекта:
kapt.use.k2=false
О любых проблемах пишите в трекер ошибок jetbrains.
Kotlin Multiplatform: новый DSL для замены плагина Gradle Application
Начиная с Gradle 8.7, плагин Application больше не совместим с плагином Kotlin Multiplatform Gradle. В Kotlin 2.1.20-RC3 введён экспериментальный DSL, который предоставляет аналогичную функциональность. Новый блок executable {} настраивает задачи выполнения и дистрибуции Gradle для целей JVM.
Перед использованием DSL добавьте следующее в свой скрипт сборки:
@OptIn(ExperimentalKotlinGradlePluginApi::class)
Затем добавьте новый блок executable {}
. Например:
kotlin {
jvm {
@OptIn(ExperimentalKotlinGradlePluginApi::class)
binaries {
// Конфигурирует задачу JavaExec с именем "runJvm" и Gradle-распределение для компиляции "main" в этой цели
executable {
mainClass.set("foo.MainKt")
}
// Конфигурирует задачу JavaExec с именем "runJvmAnother" и Gradle-распределение для компиляции "main"
executable(KotlinCompilation.MAIN_COMPILATION_NAME, "another") {
// Устанавливает другой класс
mainClass.set("foo.MainAnotherKt")
}
// Конфигурирует задачу JavaExec с именем "runJvmTest" и Gradle-распределение для компиляции "test"
executable(KotlinCompilation.TEST_COMPILATION_NAME) {
mainClass.set("foo.MainTestKt")
}
// Конфигурирует задачу JavaExec с именем "runJvmTestAnother" и Gradle-распределение для компиляции "test"
executable(KotlinCompilation.TEST_COMPILATION_NAME, "another") {
mainClass.set("foo.MainAnotherTestKt")
}
}
}
}
В этом примере плагин Distribution от Gradle применяется на первом блоке executable {}.
О любых проблемах сообщайте в трекер ошибок JetBrains или напишите в публичный канал Slack.
Kotlin/Native: новая оптимизация инлайнинга
Kotlin 2.1.20-RC3 вводит новый этап оптимизации инлайнинга, который выполняется до фазы генерации кода.
Новый этап инлайнинга в компиляторе Kotlin/Native должен быть быстрее стандартного механизма инлайнинга LLVM и улучшить производительность сгенерированного кода.
Этот этап инлайнинга пока является экспериментальным. Чтобы попробовать его в деле, используйте следующую опцию компилятора:
-Xbinary=preCodegenInlineThreshold=40
Наши эксперименты показывают, что 40 — это хорошее компромиссное значение для оптимизации. Согласно нашим бенчмаркам, это приводит к общему улучшению производительности на 9,5%. Конечно, можно попробовать другие значения.
Если вы заметили увеличение размера бинарного файла или времени компиляции, сообщите об этом в YouTrack.
Kotlin/Wasm
Пользовательские форматтеры включены по умолчанию
Ранее нужно было вручную настраивать пользовательские форматтеры для улучшения отладки в веб-браузерах при работе с кодом Kotlin/Wasm.
В этом релизе пользовательские форматтеры включены по умолчанию в сборках для разработки, и дополнительная настройка Gradle не требуется.
Чтобы использовать эту функцию, необходимо убедиться, что пользовательские форматтеры включены в инструментах разработчика браузера.
В Chrome DevTools это доступно через Настройки | Предпочтения | Консоль:

В Firefox DevTools это доступно через Настройки | Расширенные настройки:

Это изменение в первую очередь влияет на сборки для разработки. Если у вас есть особые требования для сборок для продакшн-среды, необходимо настроить конфигурацию Gradle. Добавьте следующую опцию компилятора в блок wasmJs {}
:
// build.gradle.kts
kotlin {
wasmJs {
// ...
compilerOptions {
freeCompilerArgs.add("-Xwasm-debugger-custom-formatters")
}
}
}
Миграция на Provider API для свойств Kotlin/Wasm и Kotlin/JS
Ранее свойства в расширениях Kotlin/Wasm и Kotlin/JS были изменяемыми (var
) и устанавливались непосредственно в скриптах сборки:
the().version = "2.0.0"
Теперь свойства доступны через Provider API, и для установки значений необходимо использовать функцию .set()
:
the().version.set("2.0.0")
Provider API обеспечивает ленивое вычисление значений и их правильную интеграцию с зависимостями задач, что улучшает производительность сборки.
С этим изменением прямое присваивание свойств больше не используется в пользу классов *EnvSpec
, таких как NodeJsEnvSpec
и YarnRootEnvSpec
.
Кроме того, несколько задач-синонимов были удалены, чтобы избежать путаницы:
Устаревшая задача | Замена |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Если вы используете только Kotlin/JS или Kotlin/Wasm в скриптах сборки, дополнительные действия не требуются, так как Gradle автоматически обрабатывает назначения.
Однако если вы поддерживаете плагин на основе Kotlin Gradle и ваш плагин не использует kotlin-dsl
, нужно обновить назначения свойств, чтобы использовать функцию .set()
.
Gradle
Поддержка версии 8.11
Kotlin 2.1.20-RC3 теперь совместим с последней стабильной версией Gradle 8.11 и поддерживает её функции.
Плагин Kotlin Gradle совместим с функцией Isolated Projects Gradle
Эта функция в данный момент находится в предварительной альфа-стадии в Gradle. Используйте её только с Gradle версии 8.10 или выше и исключительно для целей оценки.
С версии Kotlin 2.1.0 вы можете предварительно протестировать функцию Isolated Projects в своих проектах.
Ранее вам нужно было настроить плагин Kotlin Gradle, чтобы проект стал совместим с функцией Isolated Projects, прежде чем её можно было использовать. В Kotlin 2.1.20-RC3 этот дополнительный шаг больше не требуется.
Теперь для включения функции Isolated Projects достаточно установить системное свойство.
Функция Isolated Projects поддерживается в плагинах Kotlin Gradle как для мультиплатформенных проектов, так и для проектов, которые содержат только цели JVM или Android.
Для мультиплатформенных проектов, если после обновления вы столкнулись с проблемами в процессе сборки, вы можете отказаться от нового поведения плагина Kotlin Gradle, установив:
kotlin.kmp.isolated-projects.support=disable
Однако, если вы используете это свойство Gradle в своём мультиплатформенном проекте, вы не сможете использовать функцию Isolated Projects.
Отзыв по этой функции можете оставить в YouTrack.
Поддержка добавления пользовательских вариантов публикации Gradle
Kotlin 2.1.20-RC3 вводит поддержку добавления пользовательских вариантов публикации Gradle. Эта функция доступна для мультиплатформенных проектов и проектов, ориентированных на JVM.
Она не позволяет изменять существующие варианты Gradle и является экспериментальной. Для её использования примените аннотацию @OptIn(ExperimentalKotlinGradlePluginApi::class)
.
Чтобы добавить пользовательский вариант публикации Gradle, вызовите функцию adhocSoftwareComponent()
, которая возвращает экземпляр AdhocComponentWithVariants, который можно настроить в Kotlin DSL:
plugins {
// Поддерживаются только JVM и Multiplatform
kotlin("jvm")
// или
kotlin("multiplatform")
}
kotlin {
@OptIn(ExperimentalKotlinGradlePluginApi::class)
publishing {
// Возвращает экземпляр AdhocSoftwareComponent
adhocSoftwareComponent()
// Альтернативно, вы можете настроить AdhocSoftwareComponent в блоке DSL следующим образом
adhocSoftwareComponent {
// Добавьте ваши собственные варианты здесь, используя API AdhocSoftwareComponent
}
}
}
Стандартная библиотека
Универсальные атомарные типы
В Kotlin 2.1.20-RC3 мы вводим универсальные атомарные типы в стандартной библиотеке в пакете kotlin.concurrent.atomics
, что позволяет создавать платформонезависимый код для потокобезопасных операций. Это упрощает разработку для проектов Kotlin Multiplatform, устраняя необходимость дублировать логику, зависимую от атомарных типов, в разных исходных наборах.
Пакет kotlin.concurrent.atomics
и его элементы являются экспериментальными. Чтобы воспользоваться этой функциональностью, используйте аннотацию @OptIn(ExperimentalAtomicApi::class)
или опцию компилятора -opt-in=kotlin.ExperimentalAtomicApi
.
Вот пример, который показывает, как можно использовать AtomicInt
для безопасного подсчёта обработанных элементов в нескольких потоках:
@OptIn(ExperimentalAtomicApi::class)
suspend fun main() {
// Инициализирует атомарный счётчик для обработанных элементов
var processedItems = AtomicInt(0)
val totalItems = 100
val items = List(totalItems) { "item$it" }
// Разбивает элементы на части для обработки несколькими корутинами
val chunkSize = 20
val itemChunks = items.chunked(chunkSize)
coroutineScope {
for (chunk in itemChunks) {
launch {
for (item in chunk) {
println("Обрабатывается $item в потоке ${Thread.currentThread()}")
processedItems += 1 // Увеличивает счётчик атомарно
}
}
}
}
}
Чтобы обеспечить бесперебойную совместимость атомарных типов Kotlin и Java, API предоставляет функции расширения .asJavaAtomic()
и .asKotlinAtomic()
. На JVM атомарные типы Kotlin и Java — это одни и те же типы во время выполнения, поэтому атомарные типы Java можно преобразовывать в атомарные типы Kotlin и наоборот без дополнительных расходов.
Вот пример того, как могут работать атомарные типы Kotlin и Java вместе:
@OptIn(ExperimentalAtomicApi::class)
fun main() {
// Преобразует Kotlin AtomicInt в Java AtomicInteger
val kotlinAtomic = AtomicInt(42)
val javaAtomic: AtomicInteger = kotlinAtomic.asJavaAtomic()
println("Значение Java atomic: ${javaAtomic.get()}")
// Значение Java atomic: 42
// Преобразует Java AtomicInteger обратно в Kotlin AtomicInt
val kotlinAgain: AtomicInt = javaAtomic.asKotlinAtomic()
println("Значение Kotlin atomic: ${kotlinAgain.load()}")
// Значение Kotlin atomic: 42
}
Изменения в разборе, форматировании и сопоставимости UUID
Команда JetBrains продолжает улучшать поддержку UUID, введённую в стандартной библиотеке в версии 2.0.20.
Ранее функция parse()
принимала только UUID в формате с шестнадцатеричными числами и дефисами. В Kotlin 2.1.20-RC3 теперь можно использовать parse()
для обоих форматов — с дефисами и без них (простой шестнадцатеричный формат).
Также в этом релизе были введены функции, предназначенные для работы с форматом с дефисами:
parseHexDash()
— парсит UUID из формата с шестнадцатеричными числами и дефисами.toHexDashString()
— преобразуетUUID
вString
в формате с шестнадцатеричными числами и дефисами (аналогично функцииtoString()
).
Эти функции работают аналогично parseHex()
и toHexString()
, которые были введены ранее для шестнадцатеричного формата. Явное наименование функций для парсинга и форматирования должно улучшить читаемость кода и общий опыт работы с UUID.
UUID в Kotlin теперь поддерживает сравнение (Comparable
). Начиная с Kotlin 2.1.20-RC3, сравнивать и сортировать значения типа Uuid
можно напрямую. Это позволяет использовать операторы <
и >
, стандартные расширения библиотеки для типов, реализующих интерфейс Comparable
или их коллекций (например, sorted()
), а также передавать UUID в функции или API, которые требуют интерфейс Comparable
.
Помните, что поддержка UUID в стандартной библиотеке всё ещё является экспериментальной. Чтобы использовать её, добавьте аннотацию @OptIn(ExperimentalUuidApi::class)
или опцию компилятора -opt-in=kotlin.uuid.ExperimentalUuidApi
.
@OptIn(ExperimentalUuidApi::class)
fun main() {
// parse() принимает UUID в обычном шестнадцатеричном формате
val uuid = Uuid.parse("550e8400e29b41d4a716446655440000")
// Преобразует его в формат с дефисами и шестнадцатеричными числами
val hexDashFormat = uuid.toHexDashString()
// Выводит UUID в формате с дефисами и шестнадцатеричными числами
println(hexDashFormat)
// Выводит UUID в порядке возрастания
println(
listOf(
uuid,
Uuid.parse("780e8400e29b41d4a716446655440005"),
Uuid.parse("5ab88400e29b41d4a716446655440076")
).sorted()
)
}
Новая функциональность для отслеживания времени
Начиная с Kotlin 2.1.20-RC3, стандартная библиотека даёт возможность представлять момент времени. Эта функциональность ранее была доступна только в kotlinx-datetime
— официальной библиотеке Kotlin.
Интерфейс kotlinx.datetime.Clock
был добавлен в стандартную библиотеку как kotlin.time.Clock
, а класс kotlinx.datetime.Instant
как kotlin.time.Instant
. Эти концепции естественным образом интегрируются с пакетом времени в стандартной библиотеке, поскольку они касаются только моментов времени, в отличие от более сложной функциональности для работы с календарём и временными зонами, которая остаётся в kotlinx-datetime
.
Instant
и Clock
полезны, когда нужно точно отслеживать время без учёта временных зон или дат. Например, их можно использовать для логирования событий с метками времени, измерения продолжительности между двумя моментами времени и получения текущего момента для системных процессов.
Для обеспечения совместимости с другими языками доступны дополнительные функции-конвертеры:
.toKotlinInstant()
— преобразует значение времени в экземплярkotlin.time.Instant
..toJavaInstant()
— преобразует значениеkotlin.time.Instant
в значениеjava.time.Instant
.Instant.toJSDate()
— преобразует значениеkotlin.time.Instant
в экземпляр класса JSDate
. Это преобразование не является точным, так как JS использует миллисекундную точность для представления дат, в то время как Kotlin поддерживает разрешение до наносекунд.
Новые функции работы с временем в стандартной библиотеке всё ещё являются экспериментальными. Чтобы воспользоваться ими, используйте аннотацию @OptIn(ExperimentalTime::class)
.
import kotlin.time.*
@OptIn(ExperimentalTime::class)
fun main() {
// Получает текущий момент времени
val currentInstant = Clock.System.now()
println("Текущее время: $currentInstant")
// Находит разницу между двумя моментами времени
val pastInstant = Instant.parse("2023-01-01T00:00:00Z")
val duration = currentInstant - pastInstant
println("Время, прошедшее с 2023-01-01: $duration")
}
Для получения дополнительной информации о реализации смотрите это предложение KEEP.
Компилятор Compose: информация об исходных файлах включена по умолчанию
Плагин компилятора Compose для Gradle включает информацию о исходниках по умолчанию для всех платформ. Опция includeSourceInformation
уже была включена для Android, и это изменение унифицирует поведение плагина между платформами, а также поддерживает новые функции времени выполнения.
Не забудьте проверить, установили ли вы эту опцию с помощью freeCompilerArgs
: при использовании этой опции вместе с плагином сборка может завершиться с ошибкой из-за того, что опция установлена дважды.
Критические изменения и устаревшие функции
Для согласования Kotlin Multiplatform с предстоящими изменениями в Gradle мы постепенно выводим функцию withJava()
. Наборы исходных файлов для Java теперь создаются по умолчанию.
Как обновиться до Kotlin 2.1.20-RC3
Начиная с версии IntelliJ IDEA 2023.3 и Android Studio Iguana (2023.2.1) Canary 15, плагин Kotlin теперь распространяется как встроенный, включённый в IDE. Это означает, что плагин больше нельзя установить через JetBrains Marketplace. Встроенный плагин поддерживает предстоящие релизы Kotlin EAP.
Чтобы обновиться до новой версии Kotlin EAP, измените версию Kotlin в скриптах сборки.
Если вы хотите углубиться в мир Kotlin и повысить качество своих тестов, рекомендуем посетить открытые уроки в Otus. Узнайте, как эффективно использовать возможности Kotlin для оптимизации процессов тестирования и улучшения качества кода.
Вот темы, которые стоит посетить:
3 апреля. Оптимизация CI/CD для мобильных тестов на Kotlin: как избавиться от нестабильных тестов и ускорить развертывание? Записаться
10 апреля. Контрактное тестирование в Kotlin QA: как гарантировать, что фронтенд и бэкенд понимают друг друга? Записаться
17 апреля. Применение возможностей Kotlin в UI тестировании: как Kotlin помогает в тестировании пользовательского интерфейса. Записаться
Habrahabr.ru прочитано 13931 раз