Что нового в 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<NodeJsExtension>().version = "2.0.0"

Теперь свойства доступны через Provider API, и для установки значений необходимо использовать функцию .set():

the<NodeJsEnvSpec>().version.set("2.0.0")

Provider API обеспечивает ленивое вычисление значений и их правильную интеграцию с зависимостями задач, что улучшает производительность сборки.

С этим изменением прямое присваивание свойств больше не используется в пользу классов *EnvSpec, таких как NodeJsEnvSpec и YarnRootEnvSpec.

Кроме того, несколько задач-синонимов были удалены, чтобы избежать путаницы:

Устаревшая задача

Замена

wasmJsRun

wasmJsBrowserDevelopmentRun

wasmJsBrowserRun

wasmJsBrowserDevelopmentRun

wasmJsNodeRun

wasmJsNodeDevelopmentRun

wasmJsBrowserWebpack

wasmJsBrowserProductionWebpack или wasmJsBrowserDistribution

jsRun

jsBrowserDevelopmentRun

jsBrowserRun

jsBrowserDevelopmentRun

jsNodeRun

jsNodeDevelopmentRun

jsBrowserWebpack

jsBrowserProductionWebpack или jsBrowserDistribution

Если вы используете только 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 в экземпляр класса JS Date. Это преобразование не является точным, так как 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 помогает в тестировании пользовательского интерфейса. Записаться


ссылка на оригинал статьи https://habr.com/ru/articles/896162/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *