Spec-Zone.ru › Kotlin 2

Что нового в Kotlin 1.4.0

Выпущена: 17 августа 2020 г.

В Kotlin 1.4.0 мы добавили ряд улучшений во все компоненты, уделив особое внимание качеству и производительности. Ниже приведён список наиболее важных изменений в Kotlin 1.4.0.

Информацию о цикле выпуска Kotlin см. в разделе Процесс выпуска Kotlin.

Возможности и улучшения языка

В Kotlin 1.4.0 появилось множество новых языковых возможностей и улучшений. К ним относятся:

  • Преобразования SAM для интерфейсов Kotlin

  • Режим явного API для авторов библиотек

  • Сочетание именованных и позиционных аргументов

  • Заключительная запятая

  • Улучшения ссылок на вызываемые объекты

  • break и continue внутри when в циклах

Преобразования SAM для интерфейсов Kotlin

До Kotlin 1.4.0 преобразования SAM (Single Abstract Method) можно было применять только при работе с методами и интерфейсами Java из Kotlin. Теперь преобразования SAM можно использовать и для интерфейсов Kotlin. Для этого явно пометьте интерфейс Kotlin модификатором fun.

Преобразование SAM применяется, если вы передаёте лямбду в качестве аргумента, когда параметром ожидается интерфейс, содержащий только один абстрактный метод. В этом случае компилятор автоматически преобразует лямбду в экземпляр класса, реализующего абстрактную функцию-член.

fun interface IntPredicate {
    fun accept(i: Int): Boolean
}

val isEven = IntPredicate { it % 2 == 0 }

fun main() { 
    println("Is 7 even? - ${isEven.accept(7)}")
}

Подробнее о функциональных интерфейсах Kotlin и преобразованиях SAM.

Режим явного API для авторов библиотек

Компилятор Kotlin предлагает авторам библиотек режим явного API. В этом режиме компилятор выполняет дополнительные проверки, помогающие сделать API библиотеки более понятным и согласованным. Для объявлений, входящих в публичный API библиотеки, вводятся следующие требования:

  • Для объявлений, которые при видимости по умолчанию становятся частью публичного API, необходимо указывать модификаторы видимости. Это помогает избежать непреднамеренного включения объявлений в публичный API.

  • Для свойств и функций, входящих в публичный API, необходимо явно указывать типы. Это гарантирует, что пользователи API знают типы используемых ими элементов API.

В зависимости от конфигурации эти требования к явному API могут приводить к ошибкам (режим strict) или предупреждениям (режим warning). Чтобы сохранить читаемость и руководствоваться здравым смыслом, некоторые виды объявлений не проверяются:

  • первичные конструкторы

  • свойства классов данных

  • геттеры и сеттеры свойств

  • методы override

Режим явного API анализирует только исходный код модуля для production-сборки.

Чтобы скомпилировать модуль в режиме явного API, добавьте следующие строки в сценарий сборки Gradle:

kotlin {    
    // for strict mode
    explicitApi() 
    // or
    explicitApi = ExplicitApiMode.Strict
    
    // for warning mode
    explicitApiWarning()
    // or
    explicitApi = ExplicitApiMode.Warning
}
kotlin {    
    // for strict mode
    explicitApi() 
    // or
    explicitApi = 'strict'
    
    // for warning mode
    explicitApiWarning()
    // or
    explicitApi = 'warning'
}

При использовании компилятора командной строки включите режим явного API, добавив параметр компилятора -Xexplicit-api со значением strict или warning.

-Xexplicit-api={strict|warning}

Подробнее о режиме явного API в KEEP.

Сочетание именованных и позиционных аргументов

В Kotlin 1.3 при вызове функции с именованными аргументами все аргументы без имён (позиционные аргументы) нужно было указывать до первого именованного аргумента. Например, можно было вызвать f(1, y = 2), но нельзя было вызвать f(x = 1, 2).

Это было очень неудобно, когда все аргументы уже стояли на своих местах, но вы хотели указать имя одного из аргументов в середине. Такая возможность была особенно полезна, чтобы совершенно ясно обозначить, к какому атрибуту относится логическое значение или значение null.

В Kotlin 1.4 этого ограничения больше нет: теперь можно указать имя аргумента посреди набора позиционных аргументов. Кроме того, позиционные и именованные аргументы можно сочетать как угодно, если они остаются в правильном порядке.

fun reformat(
    str: String,
    uppercaseFirstLetter: Boolean = true,
    wordSeparator: Char = ' '
) {
    // ...
}

//Function call with a named argument in the middle
reformat("This is a String!", uppercaseFirstLetter = false , '-')

Заключительная запятая

В Kotlin 1.4 теперь можно добавлять заключительную запятую в перечисления, например в списки аргументов и параметров, элементы when и компоненты объявлений с деструктуризацией. Благодаря заключительной запятой можно добавлять новые элементы и менять их порядок, не добавляя и не удаляя запятые.

Это особенно удобно, если параметры или значения записаны в несколько строк. Добавив заключительную запятую, вы сможете легко поменять строки с параметрами или значениями местами.

fun reformat(
    str: String,
    uppercaseFirstLetter: Boolean = true,
    wordSeparator: Character = ' ', //trailing comma
) {
    // ...
}
val colors = listOf(
    "red",
    "green",
    "blue", //trailing comma
)

Улучшения ссылок на вызываемые объекты

В Kotlin 1.4 поддерживается больше вариантов использования ссылок на вызываемые объекты:

  • Ссылки на функции с параметрами, имеющими значения по умолчанию

  • Ссылки на функции в функциях, возвращающих Unit

  • Ссылки, адаптирующиеся в зависимости от количества аргументов функции

  • Преобразование suspend для ссылок на вызываемые объекты

Ссылки на функции с параметрами, имеющими значения по умолчанию

Теперь можно использовать ссылки на функции, содержащие параметры со значениями по умолчанию. Если ссылка на функцию foo не принимает аргументов, используется значение по умолчанию 0.

fun foo(i: Int = 0): String = "$i!"

fun apply(func: () -> String): String = func()

fun main() {
    println(apply(::foo))
}

Раньше для этого нужно было написать дополнительную перегрузку функции apply или foo.

// some new overload
fun applyInt(func: (Int) -> String): String = func(0) 

Ссылки на функции в функциях, возвращающих Unit

В Kotlin 1.4 в функциях, возвращающих Unit, можно использовать ссылки на функции, возвращающие любой тип. До Kotlin 1.4 в таких случаях можно было использовать только лямбда-выражения. Теперь можно использовать и лямбда-выражения, и ссылки на вызываемые объекты.

fun foo(f: () -> Unit) { }
fun returnsInt(): Int = 42

fun main() {
    foo { returnsInt() } // this was the only way to do it  before 1.4
    foo(::returnsInt) // starting from 1.4, this also works
}

Ссылки, адаптирующиеся в зависимости от количества аргументов функции

Теперь ссылки на функции можно адаптировать при передаче переменного числа аргументов (vararg). В конце списка передаваемых аргументов можно указать любое количество параметров одного типа.

fun foo(x: Int, vararg y: String) {}

fun use0(f: (Int) -> Unit) {}
fun use1(f: (Int, String) -> Unit) {}
fun use2(f: (Int, String, String) -> Unit) {}

fun test() {
    use0(::foo) 
    use1(::foo) 
    use2(::foo) 
}

Преобразование suspend для ссылок на вызываемые объекты

Начиная с версии 1.4.0, Kotlin поддерживает преобразование suspend не только для лямбд, но и для ссылок на вызываемые объекты.

fun call() {}
fun takeSuspend(f: suspend () -> Unit) {}

fun test() {
    takeSuspend { call() } // OK before 1.4
    takeSuspend(::call) // In Kotlin 1.4, it also works
}

Использование break и continue внутри when-выражений в циклах

В Kotlin 1.3 нельзя было использовать неквалифицированные break и continue внутри выражений when, находящихся в циклах. Причина заключалась в том, что эти ключевые слова были зарезервированы для возможного проваливания в выражениях when.

Поэтому, если вы хотели использовать break и continue внутри выражений when в циклах, их нужно было помечать метками, что было довольно неудобно.

fun test(xs: List<Int>) {
    LOOP@for (x in xs) {
        when (x) {
            2 -> continue@LOOP
            17 -> break@LOOP
            else -> println(x)
        }
    }
}

В Kotlin 1.4 можно использовать break и continue без меток внутри выражений when, находящихся в циклах. Они работают ожидаемым образом: завершают ближайший охватывающий цикл или переходят к следующей итерации.

fun test(xs: List<Int>) {
    for (x in xs) {
        when (x) {
            2 -> continue
            17 -> break
            else -> println(x)
        }
    }
}

Поведение с проваливанием внутри when будет проработано дополнительно.

Новые инструменты в IDE

В Kotlin 1.4 можно использовать новые инструменты IntelliJ IDEA, упрощающие разработку на Kotlin:

  • Новый гибкий мастер проектов

  • Отладчик корутин

Новый гибкий мастер проектов

Новый гибкий мастер проектов Kotlin позволяет легко создавать и настраивать проекты Kotlin разных типов, включая многоплатформенные проекты, которые без пользовательского интерфейса бывает сложно настроить.

Kotlin Project Wizard – Multiplatform project

Новый мастер проектов Kotlin прост и гибок:

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

  2. Выберите систему сборки — Gradle (Kotlin DSL или Groovy DSL), Maven или IntelliJ IDEA.
    Мастер проектов Kotlin покажет только те системы сборки, которые поддерживаются выбранным шаблоном проекта.

  3. Просмотрите структуру проекта прямо на главном экране.

Затем можно завершить создание проекта или при желании настроить проект на следующем экране:

  1. Добавьте или удалите модули и цели, поддерживаемые этим шаблоном проекта.

  2. Настройте параметры модулей и целей, например версию целевой JVM, шаблон цели и платформу тестирования.

Kotlin Project Wizard - Configure targets

В будущем мы сделаем мастер проектов Kotlin ещё гибче, добавив дополнительные параметры настройки и шаблоны.

Попробуйте новый мастер проектов Kotlin, выполнив следующие руководства:

  • Создание консольного приложения на Kotlin/JVM

  • Создание приложения Kotlin/JS для React

  • Создание приложения Kotlin/Native

Отладчик корутин

Многие уже используют корутины для асинхронного программирования. Однако до выхода Kotlin 1.4 отлаживать код с корутинами было непросто. Поскольку корутины переходили между потоками, было сложно понять, чем занимается конкретная корутина, и проверить её контекст. В некоторых случаях отслеживание шагов с помощью точек останова просто не работало. В результате для отладки кода с корутинами приходилось полагаться на журналирование или собственную память.

В Kotlin 1.4 отладка корутин стала намного удобнее благодаря новым возможностям плагина Kotlin.

Отладка поддерживается в версии kotlinx-coroutines-core 1.3.8 или новее.

В окне Отладка появилась новая вкладка Корутины. На этой вкладке можно найти информацию как о выполняющихся, так и о приостановленных корутинах. Корутины сгруппированы по диспетчерам, на которых они выполняются.

Debugging coroutines

Теперь можно:

  • Легко проверять состояние каждой корутины.

  • Просматривать значения локальных и захваченных переменных как выполняющихся, так и приостановленных корутин.

  • Просматривать полный стек создания корутины, а также стек вызовов внутри корутины. Стек включает все кадры со значениями переменных, в том числе те, которые теряются при обычной отладке.

Если вам нужен полный отчёт с состоянием каждой корутины и её стеком, щёлкните правой кнопкой мыши на вкладке Корутины, а затем выберите Получить дамп корутин. Пока дамп корутин довольно простой, но в будущих версиях Kotlin мы сделаем его более понятным и полезным.

Coroutines Dump

Подробнее об отладке корутин читайте в этой статье в блоге и в документации IntelliJ IDEA.

Новый компилятор

Новый компилятор Kotlin будет очень быстрым, объединит поддержку всех платформ и предоставит API для расширений компилятора. Это долгосрочный проект, и в Kotlin 1.4.0 мы уже сделали несколько шагов к его реализации:

  • Новый, более мощный алгоритм вывода типов включён по умолчанию.

  • Новые бэкенды JVM IR и JS IR. Они станут настройками по умолчанию после стабилизации.

Новый, более мощный алгоритм вывода типов

В Kotlin 1.4 используется новый, более мощный алгоритм вывода типов. Этот алгоритм уже можно было опробовать в Kotlin 1.3, указав параметр компилятора, а теперь он используется по умолчанию. Полный список проблем, исправленных новым алгоритмом, можно найти в YouTrack. Ниже приведены некоторые наиболее заметные улучшения:

  • Больше случаев автоматического вывода типа

  • Смарт-приведения для последнего выражения лямбды

  • Смарт-приведения для ссылок на вызываемые объекты

  • Улучшенный вывод типов для делегированных свойств

  • Преобразование SAM для интерфейсов Java с разными аргументами

  • Интерфейсы SAM Java в Kotlin

Больше случаев автоматического вывода типа

Новый алгоритм вывода типов определяет типы во многих случаях, в которых старый алгоритм требовал указывать их явно. Например, в следующем примере тип параметра лямбды it правильно выводится как String?:

//sampleStart
val rulesMap: Map<String, (String?) -> Boolean> = mapOf(
    "weak" to { it != null },
    "medium" to { !it.isNullOrBlank() },
    "strong" to { it != null && "^[a-zA-Z0-9]+$".toRegex().matches(it) }
)
//sampleEnd

fun main() {
    println(rulesMap.getValue("weak")("abc!"))
    println(rulesMap.getValue("strong")("abc"))
    println(rulesMap.getValue("strong")("abc!"))
}

В Kotlin 1.3 для этого требовалось добавить явный параметр лямбды или заменить to конструктором Pair с явными аргументами типа.

Смарт-приведения для последнего выражения лямбды

В Kotlin 1.3 последнее выражение внутри лямбды не подвергалось смарт-приведению, если не был указан ожидаемый тип. Поэтому в следующем примере Kotlin 1.3 выводит String? как тип переменной result:

val result = run {
    var str = currentValue()
    if (str == null) {
        str = "test"
    }
    str // the Kotlin compiler knows that str is not null here
}
// The type of 'result' is String? in Kotlin 1.3 and String in Kotlin 1.4

В Kotlin 1.4 благодаря новому алгоритму вывода типов последнее выражение внутри лямбды подвергается смарт-приведению, и для вывода типа лямбды используется этот новый, более точный тип. Поэтому тип переменной result становится String.

В Kotlin 1.3 для работы таких случаев часто требовалось добавлять явные приведения (например, !! или приведения типов вроде as String), а теперь они не нужны.

Смарт-приведения для ссылок на вызываемые объекты

В Kotlin 1.3 нельзя было получить доступ к ссылке на член типа, к которому было выполнено смарт-приведение. Теперь в Kotlin 1.4 это возможно:

import kotlin.reflect.KFunction

sealed class Animal
class Cat : Animal() {
    fun meow() {
        println("meow")
    }
}

class Dog : Animal() {
    fun woof() {
        println("woof")
    }
}

//sampleStart
fun perform(animal: Animal) {
    val kFunction: KFunction<*> = when (animal) {
        is Cat -> animal::meow
        is Dog -> animal::woof
    }
    kFunction.call()
}
//sampleEnd

fun main() {
    perform(Cat())
}

После смарт-приведения переменной animal к конкретным типам Cat и Dog можно использовать различные ссылки на члены animal::meow и animal::woof. После проверки типов можно обращаться к ссылкам на члены, соответствующим подтипам.

Улучшенный вывод типов для делегированных свойств

Тип делегированного свойства не учитывался при анализе выражения делегата после ключевого слова by. Например, раньше следующий код не компилировался, а теперь компилятор правильно выводит типы параметров old и new как String?:

import kotlin.properties.Delegates

fun main() {
    var prop: String? by Delegates.observable(null) { p, old, new ->
        println("$old → $new")
    }
    prop = "abc"
    prop = "xyz"
}

Преобразование SAM для интерфейсов Java с разными аргументами

Kotlin поддерживает преобразования SAM для интерфейсов Java с самого начала, но один случай не поддерживался, что иногда создавало неудобства при работе с существующими библиотеками Java. Если вы вызывали метод Java, принимающий два интерфейса SAM в качестве параметров, оба аргумента должны были быть либо лямбдами, либо обычными объектами. Нельзя было передать один аргумент в виде лямбды, а другой — в виде объекта.

Новый алгоритм устраняет эту проблему: теперь в любом случае вместо интерфейса SAM можно передать лямбду, как и следовало ожидать.

// FILE: A.java
public class A {
    public static void foo(Runnable r1, Runnable r2) {}
}
// FILE: test.kt
fun test(r1: Runnable) {
    A.foo(r1) {}  // Works in Kotlin 1.4
}

Интерфейсы SAM Java в Kotlin

В Kotlin 1.4 можно использовать интерфейсы SAM Java в Kotlin и применять к ним преобразования SAM.

import java.lang.Runnable

fun foo(r: Runnable) {}

fun test() { 
    foo { } // OK
}

В Kotlin 1.3 для выполнения преобразования SAM функцию foo выше пришлось бы объявить в коде Java.

Унифицированные бэкенды и расширяемость

В Kotlin есть три бэкенда, генерирующих исполняемые файлы: Kotlin/JVM, Kotlin/JS и Kotlin/Native. Kotlin/JVM и Kotlin/JS используют мало общего кода, поскольку разрабатывались независимо друг от друга. Kotlin/Native основан на новой инфраструктуре, построенной вокруг промежуточного представления (IR) кода Kotlin.

Сейчас мы переносим Kotlin/JVM и Kotlin/JS на одну и ту же IR. В результате все три бэкенда будут использовать общую логику и унифицированный конвейер. Это позволит реализовывать большинство возможностей, оптимизаций и исправлений ошибок всего один раз для всех платформ. Оба новых бэкенда на основе IR находятся на стадии Alpha.

Общая инфраструктура бэкендов также открывает возможности для многоплатформенных расширений компилятора. Вы сможете подключаться к конвейеру и добавлять собственную обработку и преобразования, которые будут автоматически работать на всех платформах.

Мы рекомендуем использовать новые бэкенды JVM IR и JS IR, которые сейчас находятся на стадии Alpha, и делиться с нами отзывами.

Kotlin/JVM

Kotlin 1.4.0 включает ряд улучшений, специфичных для JVM, таких как:

  • Новый IR-бэкенд JVM

  • Новые режимы генерации методов по умолчанию в интерфейсах

  • Единый тип исключения для проверок на null

  • Аннотации типов в байт-коде JVM

Новый IR-бэкенд JVM

Наряду с Kotlin/JS мы переводим Kotlin/JVM на единый IR-бэкенд, который позволяет реализовывать большинство функций и исправлений ошибок один раз для всех платформ. Вы также сможете воспользоваться этим, создавая мультиплатформенные расширения, которые будут работать на всех платформах.

В Kotlin 1.4.0 пока нет публичного API для таких расширений, но мы тесно сотрудничаем с нашими партнёрами, включая Jetpack Compose, которые уже создают плагины компилятора с помощью нашего нового бэкенда.

Мы предлагаем попробовать новый бэкенд Kotlin/JVM, который в настоящее время находится на стадии Alpha, и сообщать о проблемах и запрашивать новые функции в нашем трекере задач. Это поможет нам объединить конвейеры компилятора и быстрее предоставить сообществу Kotlin расширения компилятора, такие как Jetpack Compose.

Чтобы включить новый IR-бэкенд JVM, укажите дополнительный параметр компилятора в скрипте сборки Gradle:

kotlinOptions.useIR = true

Если вы включите Jetpack Compose, новый бэкенд JVM будет выбран автоматически — указывать параметр компилятора в kotlinOptions не потребуется.

При использовании компилятора командной строки добавьте параметр компилятора -Xuse-ir.

Код, скомпилированный новым IR-бэкендом JVM, можно использовать только при включённом новом бэкенде. В противном случае возникнет ошибка. Поэтому мы не рекомендуем авторам библиотек переключаться на новый бэкенд в рабочей среде.

Новые режимы генерации методов по умолчанию

При компиляции кода Kotlin для JVM 1.8 и выше можно было компилировать неабстрактные методы интерфейсов Kotlin в методы default Java. Для этого существовал механизм, включающий аннотацию @JvmDefault для пометки таких методов и параметр компилятора -Xjvm-default, включающий обработку этой аннотации.

В версии 1.4.0 мы добавили новый режим генерации методов по умолчанию: -Xjvm-default=all компилирует все неабстрактные методы интерфейсов Kotlin в методы Java типа default. Для совместимости с кодом, использующим интерфейсы, скомпилированные без default, мы также добавили режим all-compatibility.

Дополнительную информацию о методах по умолчанию при взаимодействии с Java см. в документации по взаимодействию и этой записи в блоге.

Единый тип исключения для проверок на null

Начиная с Kotlin 1.4.0 все проверки на null во время выполнения будут выбрасывать java.lang.NullPointerException вместо KotlinNullPointerException, IllegalStateException, IllegalArgumentException и TypeCastException. Это относится к оператору !!, проверкам параметров на null в начале метода, проверкам на null выражений платформенных типов, а также оператору as с ненулевым типом. Это не относится к проверкам на null в lateinit и явным вызовам библиотечных функций, например checkNotNull или requireNotNull.

Это изменение увеличивает число возможных оптимизаций проверок на null, которые может выполнять компилятор Kotlin или различные инструменты обработки байт-кода, например оптимизатор Android R8.

Обратите внимание, что с точки зрения разработчика изменения будут незначительными: код Kotlin будет выбрасывать исключения с теми же сообщениями об ошибках, что и раньше. Тип исключения изменится, но передаваемая информация останется прежней.

Аннотации типов в байт-коде JVM

Теперь Kotlin может генерировать аннотации типов в байт-коде JVM (для целевой версии 1.8 и выше), делая их доступными в рефлексии Java во время выполнения. Чтобы поместить аннотацию типа в байт-код, выполните следующие действия:

  1. Убедитесь, что для объявленной аннотации указаны правильная цель (ElementType.TYPE_USE в Java или AnnotationTarget.TYPE в Kotlin) и время хранения (AnnotationRetention.RUNTIME).

  2. Скомпилируйте объявление класса аннотации в байт-код JVM для целевой версии 1.8 или выше. Для этого можно указать параметр компилятора -jvm-target=1.8.

  3. Скомпилируйте код, использующий аннотацию, в байт-код JVM для целевой версии 1.8 или выше (-jvm-target=1.8) и добавьте параметр компилятора -Xemit-jvm-type-annotations.

Обратите внимание: аннотации типов из стандартной библиотеки пока не включаются в байт-код, так как стандартная библиотека скомпилирована для целевой версии 1.6.

Пока поддерживаются только основные случаи:

  • Аннотации типов параметров методов, возвращаемых типов методов и типов свойств;

  • Инвариантные проекции аргументов типа, например Smth<@Ann Foo>, Array<@Ann Foo>.

В следующем примере аннотация @Foo для типа String может быть помещена в байт-код, а затем использована кодом библиотеки:

@Target(AnnotationTarget.TYPE)
annotation class Foo

class A {
    fun foo(): @Foo String = "OK"
}

Kotlin/JS

Для платформы JS в Kotlin 1.4.0 реализованы следующие улучшения:

  • Новый DSL Gradle

  • Новый IR-бэкенд JS

Новый DSL Gradle

Плагин Gradle kotlin.js использует обновлённый DSL Gradle, который содержит ряд новых параметров конфигурации и в большей степени соответствует DSL плагина kotlin-multiplatform. Среди наиболее важных изменений:

  • Явные переключатели для создания исполняемых файлов с помощью binaries.executable(). Подробнее о запуске Kotlin/JS и его среде.

  • Настройка загрузчиков CSS и стилей webpack через конфигурацию Gradle с помощью cssSupport. Подробнее об использовании загрузчиков CSS и стилей.

  • Улучшенное управление зависимостями npm: обязательные номера версий или диапазоны версий semver, а также поддержка разрабатываемых, peer- и необязательных зависимостей npm с помощью devNpm, optionalNpm и peerNpm. Подробнее об управлении зависимостями пакетов npm непосредственно из Gradle.

  • Улучшена интеграция с Dukat — генератором внешних объявлений Kotlin. Теперь внешние объявления можно генерировать во время сборки или вручную с помощью задачи Gradle.

Новый IR-бэкенд JS

IR-бэкенд для Kotlin/JS, стабильность которого сейчас оценивается как Alpha, предоставляет новые возможности для целевой платформы Kotlin/JS. Они связаны, в частности, с уменьшением размера сгенерированного кода за счёт удаления неиспользуемого кода и улучшением взаимодействия с JavaScript и TypeScript.

Чтобы включить IR-бэкенд Kotlin/JS, задайте ключ kotlin.js.compiler=ir в gradle.properties или передайте тип компилятора IR функции js в скрипте сборки Gradle:

kotlin {
    js(IR) { // or: LEGACY, BOTH
        // ...
    }
    binaries.executable()
}

Подробную информацию о настройке нового бэкенда см. в документации по IR-компилятору Kotlin/JS.

Новая аннотация @JsExport и возможность генерировать определения TypeScript из кода Kotlin улучшают взаимодействие JavaScript и TypeScript с помощью IR-бэкенда компилятора Kotlin/JS. Это также упрощает интеграцию кода Kotlin/JS с существующими инструментами, создание гибридных приложений и использование возможностей совместного использования кода в мультиплатформенных проектах.

Подробнее о возможностях IR-бэкенда компилятора Kotlin/JS.

Kotlin/Native

В версии 1.4.0 в Kotlin/Native появилось много новых функций и улучшений, в том числе:

  • Поддержка приостанавливаемых функций Kotlin в Swift и Objective-C

  • Поддержка обобщённых типов Objective-C по умолчанию

  • Обработка исключений при взаимодействии с Objective-C/Swift

  • Генерация .dSYM для выпускных сборок на целевых платформах Apple по умолчанию

  • Улучшения производительности

  • Упрощённое управление зависимостями CocoaPods

Поддержка приостанавливаемых функций Kotlin в Swift и Objective-C

В версии 1.4.0 добавлена базовая поддержка приостанавливаемых функций в Swift и Objective-C. Теперь при компиляции модуля Kotlin в фреймворк Apple приостанавливаемые функции доступны в нём как функции с обратными вызовами (completionHandler в терминологии Swift/Objective-C). Если такие функции присутствуют в заголовочном файле сгенерированного фреймворка, их можно вызывать из кода Swift или Objective-C и даже переопределять.

Например, если написать такую функцию на Kotlin:

suspend fun queryData(id: Int): String = ...

...её можно вызвать из Swift следующим образом:

queryData(id: 17) { result, error in
   if let e = error {
       print("ERROR: \(e)")
   } else {
       print(result!)
   }
}

Подробнее об использовании приостанавливаемых функций в Swift и Objective-C.

Поддержка обобщённых типов Objective-C по умолчанию

В предыдущих версиях Kotlin поддержка обобщённых типов при взаимодействии с Objective-C была экспериментальной. Начиная с версии 1.4.0 Kotlin/Native по умолчанию генерирует фреймворки Apple с обобщёнными типами из кода Kotlin. В некоторых случаях это может нарушить работу существующего кода Objective-C или Swift, вызывающего фреймворки Kotlin. Чтобы сформировать заголовочный файл фреймворка без обобщённых типов, добавьте параметр компилятора -Xno-objc-generics.

kotlin {
    targets.withType<org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget> {
        binaries.all {
            freeCompilerArgs += "-Xno-objc-generics"
        }
    }
}

Обратите внимание: все особенности и ограничения, перечисленные в документации по взаимодействию с Objective-C, по-прежнему актуальны.

Обработка исключений при взаимодействии с Objective-C/Swift

В версии 1.4.0 мы немного изменили Swift API, генерируемый из Kotlin, в отношении преобразования исключений. Между обработкой ошибок в Kotlin и Swift есть принципиальное различие. Все исключения Kotlin являются непроверяемыми, тогда как в Swift есть только проверяемые ошибки. Поэтому, чтобы код Swift знал об ожидаемых исключениях, функции Kotlin следует помечать аннотацией @Throws, указывающей список возможных классов исключений.

При компиляции в Swift или фреймворк Objective-C функции, имеющие аннотацию @Throws или наследующие её, представляются в Objective-C как методы, возвращающие NSError*, а в Swift — как методы throws.

Раньше любые исключения, кроме RuntimeException и Error, передавались дальше как NSError. Теперь поведение изменилось: NSError выбрасывается только для исключений, являющихся экземплярами классов, указанных в качестве параметров аннотации @Throws (или их подклассов). Другие исключения Kotlin, доходящие до Swift/Objective-C, считаются необработанными и приводят к завершению программы.

Генерация .dSYM для выпускных сборок на целевых платформах Apple по умолчанию

Начиная с версии 1.4.0 компилятор Kotlin/Native по умолчанию создаёт файлы отладочных символов (.dSYMs) для выпускных бинарных файлов на платформах Darwin. Это можно отключить параметром компилятора -Xadd-light-debug=disable. На других платформах этот параметр по умолчанию отключён. Чтобы изменить его значение в Gradle, используйте:

kotlin {
    targets.withType<org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget> {
        binaries.all {
            freeCompilerArgs += "-Xadd-light-debug={enable|disable}"
        }
    }
}

Подробнее о символизации отчётов о сбоях.

Улучшения производительности

В Kotlin/Native появилось несколько улучшений производительности, ускоряющих как процесс разработки, так и выполнение программ. Вот некоторые примеры:

  • Для ускорения выделения памяти под объекты теперь доступен аллокатор памяти mimalloc в качестве альтернативы системному аллокатору. В некоторых тестах mimalloc работает до двух раз быстрее. Сейчас использование mimalloc в Kotlin/Native является экспериментальным; его можно включить с помощью параметра компилятора -Xallocator=mimalloc.

  • Мы переработали сборку библиотек взаимодействия с C. Благодаря новым инструментам Kotlin/Native создаёт такие библиотеки до четырёх раз быстрее, а размер артефактов составляет от 25 до 30% прежнего.

  • Общая производительность во время выполнения повысилась благодаря оптимизации GC. Улучшение будет особенно заметно в проектах с большим количеством долгоживущих объектов. Коллекции HashMap и HashSet теперь работают быстрее благодаря устранению избыточной упаковки.

  • В версии 1.3.70 мы представили две новые возможности для ускорения компиляции Kotlin/Native: кэширование зависимостей проекта и запуск компилятора из демона Gradle. С тех пор нам удалось исправить множество проблем и повысить общую стабильность этих возможностей.

Упрощённое управление зависимостями CocoaPods

Раньше после интеграции проекта с менеджером зависимостей CocoaPods можно было собирать часть проекта для iOS, macOS, watchOS или tvOS только в Xcode, отдельно от остальных частей мультиплатформенного проекта. Остальные части можно было собирать в IntelliJ IDEA.

Кроме того, при каждом добавлении зависимости от библиотеки Objective-C, хранящейся в CocoaPods (библиотеки Pod), приходилось переключаться из IntelliJ IDEA в Xcode, вызывать pod install и запускать там сборку Xcode.

Теперь зависимостями Pod можно управлять прямо в IntelliJ IDEA, пользуясь такими преимуществами работы с кодом, как подсветка и автодополнение. Также можно собирать весь проект Kotlin с помощью Gradle, не переключаясь в Xcode. Это значит, что открывать Xcode нужно только для написания кода Swift/Objective-C или запуска приложения на симуляторе либо устройстве.

Теперь также можно работать с локально сохранёнными библиотеками Pod.

В зависимости от потребностей можно добавить зависимости между:

  • Проектом Kotlin и библиотеками Pod, хранящимися удалённо в репозитории CocoaPods или локально на вашем компьютере.

  • Pod на Kotlin (проектом Kotlin, используемым как зависимость CocoaPods) и проектом Xcode с одной или несколькими целевыми платформами.

Выполните первоначальную настройку, а затем при добавлении новой зависимости в cocoapods просто повторно импортируйте проект в IntelliJ IDEA. Новая зависимость будет добавлена автоматически. Никаких дополнительных действий не требуется.

Узнайте, как добавлять зависимости.

Kotlin Multiplatform

Поддержка мультиплатформенных проектов находится на стадии Alpha. В будущем она может измениться несовместимым образом, и может потребоваться ручная миграция. Мы будем рады вашим отзывам на YouTrack.

Kotlin Multiplatform сокращает время, затрачиваемое на написание и поддержку одинакового кода для разных платформ, сохраняя гибкость и преимущества нативного программирования. Мы продолжаем вкладывать усилия в развитие возможностей мультиплатформенной разработки и их улучшение:

  • Совместное использование кода для нескольких целевых платформ с иерархической структурой проекта

  • Использование нативных библиотек в иерархической структуре

  • Указание зависимостей kotlinx только один раз

Для мультиплатформенных проектов требуется Gradle 6.0 или более поздней версии.

Совместное использование кода для нескольких целевых платформ с иерархической структурой проекта

Благодаря поддержке новой иерархической структуры проекта можно совместно использовать код для нескольких платформ в мультиплатформенном проекте.

Раньше любой код, добавленный в мультиплатформенный проект, можно было разместить либо в платформо-зависимом наборе исходного кода, ограниченном одной целевой платформой и недоступном для повторного использования другими платформами, либо в общем наборе исходного кода, например commonMain или commonTest, который использовался всеми платформами проекта. В общем наборе исходного кода обращаться к платформо-зависимому API можно было только с помощью объявления expect, требующего платформо-зависимых реализаций actual.

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

Например, в типичном мультиплатформенном проекте для iOS есть две целевые платформы, связанные с iOS: одна для устройств iOS ARM64, другая — для симулятора x64. У них разные платформо-зависимые наборы исходного кода, но на практике код для устройства и симулятора редко различается, а их зависимости во многом совпадают. Поэтому код для iOS можно было бы использовать совместно.

Очевидно, что в такой конфигурации было бы желательно иметь общий набор исходного кода для двух целевых платформ iOS с кодом Kotlin/Native, который при этом мог бы напрямую обращаться к любым API, общим для устройства iOS и симулятора.

Code shared for iOS targets

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

Для распространённых сочетаний целевых платформ можно создать иерархическую структуру с помощью сокращений для целевых платформ. Например, создайте две целевые платформы iOS и показанный выше общий набор исходного кода с помощью сокращения ios():

kotlin {
    ios() // iOS device and simulator targets; iosMain and iosTest source sets
}

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

Hierarchical structure
kotlin{
    sourceSets {
        val desktopMain by creating {
            dependsOn(commonMain)
        }
        val linuxX64Main by getting {
            dependsOn(desktopMain)
        }
        val mingwX64Main by getting {
            dependsOn(desktopMain)
        }
        val macosX64Main by getting {
            dependsOn(desktopMain)
        }
    }
}
kotlin {
    sourceSets {
        desktopMain {
            dependsOn(commonMain)
        }
        linuxX64Main {
            dependsOn(desktopMain)
        }
        mingwX64Main {
            dependsOn(desktopMain)
        }
        macosX64Main {
            dependsOn(desktopMain)
        }
    }
}

Благодаря иерархической структуре проекта библиотеки также могут предоставлять общие API для части целевых платформ. Подробнее о совместном использовании кода в библиотеках.

Использование нативных библиотек в иерархической структуре

В наборах исходного кода, общих для нескольких нативных целевых платформ, можно использовать платформо-зависимые библиотеки, например Foundation, UIKit и POSIX. Это позволяет совместно использовать больше нативного кода без ограничений, связанных с платформо-зависимыми зависимостями.

Дополнительные действия не требуются — всё выполняется автоматически. IntelliJ IDEA поможет обнаружить общие объявления, которые можно использовать в общем коде.

Подробнее об использовании платформо-зависимых библиотек.

Указание зависимостей только один раз

Теперь вместо указания зависимостей от разных вариантов одной и той же библиотеки в общих и платформо-зависимых наборах исходного кода, где она используется, следует указать зависимость только один раз в общем наборе исходного кода.

kotlin {
    sourceSets {
        val commonMain by getting {
            dependencies {
                implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.11.0")
            }
        }
    }
}
kotlin {
    sourceSets {
        commonMain {
            dependencies {
                implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.11.0'
            }
        }
    }
}

Не используйте имена артефактов библиотек kotlinx с суффиксами, указывающими платформу, например -common, -native и подобные: они больше НЕ поддерживаются. Вместо этого используйте базовое имя артефакта библиотеки — в примере выше это kotlinx-coroutines-core.

Однако это изменение пока не затрагивает:

  • Библиотеку stdlib — начиная с Kotlin 1.4.0 зависимость от stdlib добавляется автоматически.

  • Библиотеку kotlin.test — по-прежнему следует использовать test-common и test-annotations-common. Эти зависимости будут рассмотрены позднее.

Если зависимость нужна только для конкретной платформы, можно использовать платформо-зависимые варианты стандартных библиотек и библиотек kotlinx с такими суффиксами, как -jvm или -js, например kotlinx-coroutines-core-jvm.

Подробнее о настройке зависимостей.

Улучшения проектов Gradle

Помимо возможностей и улучшений проектов Gradle, относящихся к Kotlin Multiplatform, Kotlin/JVM, Kotlin/Native и Kotlin/JS, есть несколько изменений, применимых ко всем проектам Kotlin Gradle:

  • Зависимость от стандартной библиотеки теперь добавляется по умолчанию

  • Для проектов Kotlin требуется актуальная версия Gradle

  • Улучшена поддержка Kotlin Gradle DSL в IDE

Зависимость от стандартной библиотеки добавляется по умолчанию

Вам больше не нужно объявлять зависимость от библиотеки stdlib ни в одном проекте Kotlin Gradle, включая мультиплатформенные проекты. Зависимость добавляется по умолчанию.

Автоматически добавленная стандартная библиотека будет иметь ту же версию, что и плагин Kotlin Gradle, поскольку их версии совпадают.

Для платформенно-зависимых наборов исходного кода используется соответствующий вариант библиотеки для конкретной платформы, а для остальных добавляется общая стандартная библиотека. Плагин Kotlin Gradle выберет подходящую стандартную библиотеку JVM в зависимости от параметра компилятора kotlinOptions.jvmTarget в сценарии сборки Gradle.

Узнайте, как изменить поведение по умолчанию.

Минимальная версия Gradle для проектов Kotlin

Чтобы воспользоваться новыми возможностями в проектах Kotlin, обновите Gradle до последней версии. Для мультиплатформенных проектов требуется Gradle 6.0 или более поздняя версия, а остальные проекты Kotlin работают с Gradle 5.4 или более поздней версии.

Улучшенная поддержка *.gradle.kts в IDE

В версии 1.4.0 мы продолжили улучшать поддержку в IDE сценариев Kotlin DSL для Gradle (файлов *.gradle.kts). Вот что нового в этой версии:

  • Явная загрузка конфигураций сценариев для повышения производительности. Ранее изменения, внесенные в сценарий сборки, загружались автоматически в фоновом режиме. Чтобы повысить производительность, в версии 1.4.0 мы отключили автоматическую загрузку конфигурации сценария сборки. Теперь IDE загружает изменения только после их явного применения.

    В версиях Gradle ниже 6.0 необходимо вручную загрузить конфигурацию сценария, нажав Загрузить конфигурацию в редакторе.

    *.gradle.kts – Load Configuration

    В Gradle 6.0 и выше можно явно применить изменения, нажав Загрузить изменения Gradle или повторно импортировав проект Gradle.

    В IntelliJ IDEA 2020.1 с Gradle 6.0 и выше мы добавили еще одно действие — Загрузить конфигурации сценариев, которое загружает изменения конфигураций сценариев без обновления всего проекта. Это занимает гораздо меньше времени, чем повторный импорт всего проекта.

    *.gradle.kts – Load Script Changes and Load Gradle Changes

    Вам также следует нажать Загрузить конфигурации сценариев для вновь созданных сценариев или при первом открытии проекта с новым плагином Kotlin.

    В Gradle 6.0 и выше теперь можно загружать все сценарии сразу, в отличие от прежней реализации, в которой они загружались по отдельности. Поскольку для каждого запроса требуется выполнить фазу конфигурации Gradle, это может быть ресурсоемкой операцией для крупных проектов Gradle.

    В настоящее время такая загрузка доступна только для файлов build.gradle.kts и settings.gradle.kts (проголосуйте за соответствующую задачу). Чтобы включить подсветку для init.gradle.kts или примененных плагинов сценариев, используйте старый механизм — добавьте их в отдельные сценарии. Конфигурация этих сценариев будет загружаться отдельно, когда это потребуется. Для таких сценариев также можно включить автоматическую перезагрузку.

    *.gradle.kts – Add to standalone scripts
  • Улучшенная отчетность об ошибках. Ранее ошибки Gradle Daemon можно было увидеть только в отдельных файлах журнала. Теперь Gradle Daemon возвращает всю информацию об ошибках напрямую и показывает ее в окне инструмента сборки. Это экономит ваше время и силы.

Стандартная библиотека

Ниже приведен список наиболее значительных изменений стандартной библиотеки Kotlin в версии 1.4.0:

  • Общий API обработки исключений

  • Новые функции для массивов и коллекций

  • Функции для работы со строками

  • Битовые операции

  • Улучшения делегированных свойств

  • Преобразование KType в Java Type

  • Конфигурации Proguard для рефлексии Kotlin

  • Улучшение существующего API

  • Дескрипторы module-info для артефактов stdlib

  • Устаревшие элементы

  • Исключение устаревших экспериментальных сопрограмм

Общий API обработки исключений

Следующие элементы API перенесены в общую библиотеку:

  • Функция расширения Throwable.stackTraceToString(), возвращающая подробное описание этого объекта throwable с трассировкой стека, и Throwable.printStackTrace(), выводящая это описание в стандартный поток ошибок.

  • Функция Throwable.addSuppressed(), позволяющая указать исключения, подавленные при создании этого исключения, и свойство Throwable.suppressedExceptions, возвращающее список всех подавленных исключений.

  • Аннотация @Throws, перечисляющая типы исключений, которые будут проверяться при компиляции функции в метод платформы (на JVM или нативных платформах).

Новые функции для массивов и коллекций

Коллекции

В версии 1.4.0 стандартная библиотека включает ряд полезных функций для работы с коллекциями:

  • setOfNotNull() создает множество из всех непустых элементов переданных аргументов.

    fun main() {
    //sampleStart
        val set = setOfNotNull(null, 1, 2, 0, null)
        println(set)
    //sampleEnd
    }
    
  • shuffled() для последовательностей.

    fun main() {
    //sampleStart
        val numbers = (0 until 50).asSequence()
        val result = numbers.map { it * 2 }.shuffled().take(5)
        println(result.toList()) //five random even numbers below 100
    //sampleEnd
    }
    
  • Варианты *Indexed() для onEach() и flatMap(). Операция, применяемая ими к элементам коллекции, получает индекс элемента в качестве параметра.

    fun main() {
    //sampleStart
        listOf("a", "b", "c", "d").onEachIndexed {
            index, item -> println(index.toString() + ":" + item)
        }
    
       val list = listOf("hello", "kot", "lin", "world")
              val kotlin = list.flatMapIndexed { index, item ->
                  if (index in 1..2) item.toList() else emptyList() 
              }
    //sampleEnd
              println(kotlin)
    }
    
  • Варианты *OrNull(): randomOrNull(), reduceOrNull() и reduceIndexedOrNull(). Для пустых коллекций они возвращают null.

    fun main() {
    //sampleStart
         val empty = emptyList<Int>()
         empty.reduceOrNull { a, b -> a + b }
         //empty.reduce { a, b -> a + b } // Exception: Empty collection can't be reduced.
    //sampleEnd
    }
    
  • runningFold(), его синоним scan() и runningReduce() последовательно применяют заданную операцию к элементам коллекции, как fold() и reduce(); отличие состоит в том, что эти новые функции возвращают всю последовательность промежуточных результатов.

    fun main() {
    //sampleStart
        val numbers = mutableListOf(0, 1, 2, 3, 4, 5)
        val runningReduceSum = numbers.runningReduce { sum, item -> sum + item }
        val runningFoldSum = numbers.runningFold(10) { sum, item -> sum + item }
    //sampleEnd
        println(runningReduceSum.toString())
        println(runningFoldSum.toString())
    }
    
  • sumOf() принимает функцию-селектор и возвращает сумму ее значений для всех элементов коллекции. sumOf() может вычислять суммы типов Int, Long, Double, UInt и ULong. На JVM также доступны BigInteger и BigDecimal.

    data class OrderItem(val name: String, val price: Double, val count: Int)
    
    fun main() {
    //sampleStart
        val order = listOf<OrderItem>(
            OrderItem("Cake", price = 10.0, count = 1),
            OrderItem("Coffee", price = 2.5, count = 3),
            OrderItem("Tea", price = 1.5, count = 2))
    
        val total = order.sumOf { it.price * it.count } // Double
        val count = order.sumOf { it.count } // Int
    //sampleEnd
        println("You've ordered $count items that cost $total in total")
    }
    
  • Функции min() и max() переименованы в minOrNull() и maxOrNull() в соответствии с соглашениями об именовании, используемыми во всем API коллекций Kotlin. Суффикс *OrNull в имени функции означает, что она возвращает null, если коллекция-получатель пуста. То же относится к minBy(), maxBy(), minWith(), maxWith() — в версии 1.4 у них есть синонимы *OrNull().

  • Новые функции расширения minOf() и maxOf() возвращают минимальное и максимальное значение заданной функции-селектора для элементов коллекции.

    data class OrderItem(val name: String, val price: Double, val count: Int)
    
    fun main() {
    //sampleStart
        val order = listOf<OrderItem>(
            OrderItem("Cake", price = 10.0, count = 1),
            OrderItem("Coffee", price = 2.5, count = 3),
            OrderItem("Tea", price = 1.5, count = 2))
        val highestPrice = order.maxOf { it.price }
    //sampleEnd
        println("The most expensive item in the order costs $highestPrice")
    }
    

    Также доступны minOfWith() и maxOfWith(), принимающие в качестве аргумента Comparator, а также варианты *OrNull() всех четырех функций, возвращающие null для пустых коллекций.

  • Новые перегрузки для flatMap и flatMapTo позволяют использовать преобразования с типами возвращаемых значений, отличающимися от типа получателя, а именно:

    • Преобразования в Sequence для Iterable, Array и Map

    • Преобразования в Iterable для Sequence

    fun main() {
    //sampleStart
        val list = listOf("kot", "lin")
        val lettersList = list.flatMap { it.asSequence() }
        val lettersSeq = list.asSequence().flatMap { it.toList() }    
    //sampleEnd
        println(lettersList)
        println(lettersSeq.toList())
    }
    
  • Функции removeFirst() и removeLast() позволяют удалять элементы из изменяемых списков, а их варианты *orNull() выполняют аналогичные действия.

Массивы

Чтобы обеспечить единообразие при работе с контейнерами разных типов, мы также добавили новые функции для массивов:

  • shuffle() располагает элементы массива в случайном порядке.

  • onEach() выполняет заданное действие над каждым элементом массива и возвращает сам массив.

  • associateWith() и associateWithTo() создают отображения, используя элементы массива в качестве ключей.

  • reverse() для поддиапазонов массива меняет порядок элементов в поддиапазоне на обратный.

  • sortDescending() для поддиапазонов массива сортирует элементы поддиапазона в порядке убывания.

  • sort() и sortWith() для поддиапазонов массива теперь доступны в общей библиотеке.

fun main() {
//sampleStart
    var language = ""
    val letters = arrayOf("k", "o", "t", "l", "i", "n")
    val fileExt = letters.onEach { language += it }
       .filterNot { it in "aeuio" }.take(2)
       .joinToString(prefix = ".", separator = "")
    println(language) // "kotlin"
    println(fileExt) // ".kt"

    letters.shuffle()
    letters.reverse(0, 3)
    letters.sortDescending(2, 5)
    println(letters.contentToString()) // [k, o, t, l, i, n]
//sampleEnd
}

Кроме того, добавлены новые функции для преобразования между CharArray/ByteArray и String:

  • ByteArray.decodeToString() и String.encodeToByteArray()

  • CharArray.concatToString() и String.toCharArray()

fun main() {
//sampleStart
	val str = "kotlin"
    val array = str.toCharArray()
    println(array.concatToString())
//sampleEnd
}

ArrayDeque

Мы также добавили класс ArrayDeque — реализацию двусторонней очереди. Двусторонняя очередь позволяет добавлять и удалять элементы как в начале, так и в конце очереди за амортизированное константное время. Вы можете использовать двустороннюю очередь по умолчанию, когда в коде вам нужна очередь или стек.

fun main() {
    val deque = ArrayDeque(listOf(1, 2, 3))

    deque.addFirst(0)
    deque.addLast(4)
    println(deque) // [0, 1, 2, 3, 4]

    println(deque.first()) // 0
    println(deque.last()) // 4

    deque.removeFirst()
    deque.removeLast()
    println(deque) // [1, 2, 3]
}

В реализации ArrayDeque используется расширяемый массив: содержимое хранится в кольцевом буфере, Array, а размер этого Array увеличивается только при его заполнении.

Функции для работы со строками

Стандартная библиотека версии 1.4.0 включает ряд улучшений API для работы со строками:

  • StringBuilder получил полезные новые функции расширения: set(), setRange(), deleteAt(), deleteRange(), appendRange() и другие.

        fun main() {
        //sampleStart
            val sb = StringBuilder("Bye Kotlin 1.3.72")
            sb.deleteRange(0, 3)
            sb.insertRange(0, "Hello", 0 ,5)
            sb.set(15, '4')
            sb.setRange(17, 19, "0")
            print(sb.toString())
        //sampleEnd
        }
    
  • Некоторые существующие функции StringBuilder доступны в общей библиотеке. Среди них append(), insert(), substring(), setLength() и другие.

  • В общую библиотеку добавлены новые функции Appendable.appendLine() и StringBuilder.appendLine(). Они заменяют JVM-функции appendln() этих классов.

    fun main() {
    //sampleStart
        println(buildString {
            appendLine("Hello,")
            appendLine("world")
        })
    //sampleEnd
    }
    

Битовые операции

Новые функции для работы с битами:

  • countOneBits()

  • countLeadingZeroBits()

  • countTrailingZeroBits()

  • takeHighestOneBit()

  • takeLowestOneBit()

  • rotateLeft() и rotateRight() (экспериментальные)

fun main() {
//sampleStart
    val number = "1010000".toInt(radix = 2)
    println(number.countOneBits())
    println(number.countTrailingZeroBits())
    println(number.takeHighestOneBit().toString(2))
//sampleEnd
}

Улучшения делегированных свойств

В версии 1.4.0 мы добавили новые возможности, чтобы сделать работу с делегированными свойствами в Kotlin удобнее:

  • Теперь свойство можно делегировать другому свойству.

  • Новый интерфейс PropertyDelegateProvider помогает создавать поставщиков делегатов в одном объявлении.

  • Теперь ReadWriteProperty расширяет ReadOnlyProperty, поэтому оба интерфейса можно использовать для свойств только для чтения.

Помимо нового API, мы оптимизировали код, уменьшив размер байт-кода. Эти оптимизации описаны в этой записи блога.

Подробнее о делегированных свойствах.

Преобразование KType в Java Type

Новое свойство расширения KType.javaType (пока экспериментальное) в stdlib позволяет получить java.lang.reflect.Type из типа Kotlin без использования всей зависимости kotlin-reflect.

import kotlin.reflect.javaType
import kotlin.reflect.typeOf

@OptIn(ExperimentalStdlibApi::class)
inline fun <reified T> accessReifiedTypeArg() {
   val kType = typeOf<T>()
   println("Kotlin type: $kType")
   println("Java type: ${kType.javaType}")
}

@OptIn(ExperimentalStdlibApi::class)
fun main() {
   accessReifiedTypeArg<String>()
   // Kotlin type: kotlin.String
   // Java type: class java.lang.String
  
   accessReifiedTypeArg<List<String>>()
   // Kotlin type: kotlin.collections.List<kotlin.String>
   // Java type: java.util.List<java.lang.String>
}

Конфигурации Proguard для рефлексии Kotlin

Начиная с версии 1.4.0, мы включили конфигурации Proguard/R8 для рефлексии Kotlin в kotlin-reflect.jar. Благодаря этому большинство проектов Android, использующих R8 или Proguard, должны работать с kotlin-reflect без дополнительной настройки. Вам больше не нужно копировать правила Proguard для внутренних компонентов kotlin-reflect. Однако не забывайте, что все API, которые вы собираетесь использовать для рефлексии, по-прежнему необходимо перечислить явно.

Улучшение существующего API

  • Теперь несколько функций работают с получателями со значением null, например:

    • toBoolean() для строк

    • contentEquals(), contentHashcode(), contentToString() для массивов

  • NaN, NEGATIVE_INFINITY и POSITIVE_INFINITY в Double и Float теперь определены как const, поэтому их можно использовать в качестве аргументов аннотаций.

  • Новые константы SIZE_BITS и SIZE_BYTES в Double и Float содержат количество битов и байтов, используемых для представления экземпляра типа в двоичном виде.

  • Функции верхнего уровня maxOf() и minOf() теперь могут принимать переменное число аргументов (vararg).

Дескрипторы module-info для артефактов stdlib

В Kotlin 1.4.0 в артефакты стандартной библиотеки по умолчанию добавлена информация о модулях module-info.java. Благодаря этому их можно использовать с инструментом jlink, который создает пользовательские образы среды выполнения Java, содержащие только необходимые приложению модули платформы. Ранее jlink уже можно было использовать с артефактами стандартной библиотеки Kotlin, но для этого требовались отдельные артефакты с классификатором «modular», а настроить их было непросто.
В Android используйте Android Gradle plugin версии 3.2 или выше, который корректно обрабатывает JAR-файлы с module-info.

Устаревшие элементы

toShort() и toByte() для Double и Float

Мы пометили функции toShort() и toByte() для Double и Float как устаревшие, поскольку из-за узкого диапазона значений и меньшего размера переменных они могли приводить к неожиданным результатам.

Чтобы преобразовать числа с плавающей точкой в Byte или Short, используйте преобразование в два этапа: сначала преобразуйте их в Int, а затем — в целевой тип.

contains(), indexOf() и lastIndexOf() для массивов чисел с плавающей точкой

Мы пометили функции расширения contains(), indexOf() и lastIndexOf() для FloatArray и DoubleArray как устаревшие, поскольку они используют равенство по стандарту IEEE 754, которое в некоторых крайних случаях противоречит равенству при полном порядке. Подробнее см. эту задачу.

Функции min() и max() для коллекций

Мы пометили функции для коллекций min() и max() как устаревшие в пользу minOrNull() и maxOrNull(), которые точнее отражают их поведение: возвращение null для пустых коллекций. Подробнее см. эту задачу.

Исключение устаревших экспериментальных сопрограмм

API kotlin.coroutines.experimental был объявлен устаревшим в пользу kotlin.coroutines в версии 1.3.0. В версии 1.4.0 мы завершаем цикл устаревания kotlin.coroutines.experimental и удаляем его из стандартной библиотеки. Для тех, кто продолжает использовать его на JVM, мы предоставили артефакт совместимости kotlin-coroutines-experimental-compat.jar со всеми API экспериментальных сопрограмм. Мы опубликовали его в Maven и включили в дистрибутив Kotlin вместе со стандартной библиотекой.

Стабильная сериализация JSON

Вместе с Kotlin 1.4.0 мы выпускаем первую стабильную версию kotlinx.serialization — 1.0.0-RC. Мы рады объявить стабильным API сериализации JSON в kotlinx-serialization-core (ранее известном как kotlinx-serialization-runtime). Библиотеки для других форматов сериализации, а также некоторые расширенные компоненты основной библиотеки, остаются экспериментальными.

Мы значительно переработали API сериализации JSON, чтобы сделать его более единообразным и удобным. В дальнейшем мы продолжим развивать API сериализации JSON с сохранением обратной совместимости. Однако, если вы использовали предыдущие версии, при переходе на 1.0.0-RC вам придется переписать часть кода. В этом вам поможет Руководство по сериализации Kotlin — полный комплект документации по kotlinx.serialization. В нем описано использование наиболее важных возможностей, а также приведены рекомендации по решению возможных проблем.

Примечание: kotlinx-serialization 1.0.0-RC работает только с компилятором Kotlin 1.4. Более ранние версии компилятора несовместимы.

Скрипты и REPL

В версии 1.4.0 написание скриптов на Kotlin получило ряд функциональных улучшений и улучшений производительности, а также другие обновления. Вот некоторые из основных изменений:

  • Новый API разрешения зависимостей

  • Новый API REPL

  • Кэш скомпилированных скриптов

  • Переименование артефактов

Чтобы помочь вам лучше познакомиться со скриптами на Kotlin, мы подготовили проект с примерами. В нем представлены примеры стандартных скриптов (*.main.kts), а также примеры использования API Kotlin Scripting и пользовательских определений скриптов. Попробуйте их и поделитесь отзывами в нашем трекере задач.

Новый API разрешения зависимостей

В версии 1.4.0 мы представили новый API для разрешения внешних зависимостей (например, артефактов Maven), а также его реализации. API опубликован в новых артефактах kotlin-scripting-dependencies и kotlin-scripting-dependencies-maven. Прежняя функциональность разрешения зависимостей в библиотеке kotlin-script-util теперь объявлена устаревшей.

Новый API REPL

Новый экспериментальный API REPL теперь является частью Kotlin Scripting API. В опубликованных артефактах также доступны несколько его реализаций, некоторые из которых поддерживают расширенные возможности, например автодополнение кода. Мы используем этот API в ядре Kotlin для Jupyter, и теперь вы можете попробовать его в собственных оболочках и REPL.

Кэш скомпилированных скриптов

Kotlin Scripting API теперь позволяет реализовать кэш скомпилированных скриптов, значительно ускоряющий последующие запуски неизмененных скриптов. В нашей стандартной расширенной реализации скриптов kotlin-main-kts уже есть собственный кэш.

Переименование артефактов

Чтобы устранить путаницу в названиях артефактов, мы переименовали kotlin-scripting-jsr223-embeddable и kotlin-scripting-jvm-host-embeddable в kotlin-scripting-jsr223 и kotlin-scripting-jvm-host. Эти артефакты зависят от артефакта kotlin-compiler-embeddable, который включает сторонние библиотеки в собственное пространство имен, предотвращая конфликты при их использовании. Благодаря переименованию использование kotlin-compiler-embeddable (в целом более безопасного варианта) становится вариантом по умолчанию для артефактов скриптов. Если по какой-либо причине вам нужны артефакты, зависящие от библиотеки kotlin-compiler без изменения пространства имен, используйте версии артефактов с суффиксом -unshaded, например kotlin-scripting-jsr223-unshaded. Обратите внимание: переименование касается только артефактов скриптов, предназначенных для непосредственного использования; названия остальных артефактов не изменились.

Переход на Kotlin 1.4.0

Инструменты миграции плагина Kotlin помогают перенести проекты с предыдущих версий Kotlin на версию 1.4.0.

Просто измените версию Kotlin на 1.4.0 и повторно импортируйте проект Gradle или Maven. Затем IDE предложит выполнить миграцию.

Если вы согласитесь, будут запущены проверки кода для миграции: они проверят ваш код и предложат исправления для всего, что не работает или не рекомендуется в версии 1.4.0.

Run migration

Проверки кода имеют разные уровни серьезности, чтобы вы могли решить, какие предложения принять, а какие проигнорировать.

Migration inspections

Kotlin 1.4.0 — это выпуск с новыми возможностями, поэтому в нем могут появиться несовместимые изменения языка. Подробный список таких изменений см. в руководстве по совместимости с Kotlin 1.4.

20 мая 2026
Что нового в Kotlin 1.4.20Руководство по совместимости с Kotlin 1.4.x

© 2010–2026 JetBrains s.r.o. and Kotlin Programming Language contributors
Licensed under the Apache License, Version 2.0.
https://kotlinlang.org/docs/whatsnew14.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API