Spec-Zone.ru › Kotlin 1.9

Что нового в Kotlin 1.3

Выпущено: 29 октября 2018 года

Выпуск сопроцедур

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

Kotlin 1.3 включает вызываемые ссылки на приостановленные функции и поддержку сопроцедур в API рефлексии.

Kotlin/Native

Kotlin 1.3 продолжает улучшать и дорабатывать целевую платформу Native. Подробности см. в обзоре Kotlin/Native.

Многоплатформенные проекты

В версии 1.3 мы полностью переработали модель многоплатформенных проектов для повышения выразительности и гибкости, а также для упрощения совместного использования общего кода. Кроме того, Kotlin/Native теперь поддерживается как одна из платформ!

Основные отличия от старой модели:

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

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

  • Изменена конфигурация зависимостей; теперь зависимости указываются отдельно для каждого корневого каталога.

  • Наборы исходного кода теперь можно совместно использовать между произвольным подмножеством платформ (например, в модуле, который ориентирован на JS, Android и iOS, вы можете иметь набор исходного кода, который совместно используется только между Android и iOS).

  • Публикация многоплатформенных библиотек теперь поддерживается.

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

Контракты

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

fun foo(s: String?) {
    if (s != null) s.length // Compiler automatically casts 's' to 'String'
}

Однако, как только эти проверки извлекаются в отдельную функцию, все интеллектуальные преобразования типов сразу исчезают:

fun String?.isNotNull(): Boolean = this != null

fun foo(s: String?) {
    if (s.isNotNull()) s.length // No smartcast :(
}

Чтобы улучшить поведение в таких случаях, Kotlin 1.3 вводит экспериментальный механизм под названием контракты.

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

  • Улучшение анализа интеллектуальных преобразований типов путём объявления связи между результатом вызова функции и значениями переданных аргументов:

fun require(condition: Boolean) {
    // This is a syntax form which tells the compiler:
    // "if this function returns successfully, then the passed 'condition' is true"
    contract { returns() implies condition }
    if (!condition) throw IllegalArgumentException(...)
}

fun foo(s: String?) {
    require(s is String)
    // s is smartcast to 'String' here, because otherwise
    // 'require' would have thrown an exception
}
  • Улучшение анализа инициализации переменных при наличии функций высшего порядка:

fun synchronize(lock: Any?, block: () -> Unit) {
    // It tells the compiler:
    // "This function will invoke 'block' here and now, and exactly one time"
    contract { callsInPlace(block, EXACTLY_ONCE) }
}

fun foo() {
    val x: Int
    synchronize(lock) {
        x = 42 // Compiler knows that lambda passed to 'synchronize' is called
               // exactly once, so no reassignment is reported
    }
    println(x) // Compiler knows that lambda will be definitely called, performing
               // initialization, so 'x' is considered to be initialized here
}

Контракты в stdlib

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

//sampleStart
fun bar(x: String?) {
    if (!x.isNullOrEmpty()) {
        println("length of '$x' is ${x.length}") // Yay, smartcast to not-null!
    }
}
//sampleEnd
fun main() {
    bar(null)
    bar("42")
}

Настраиваемые контракты

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

Настраиваемые контракты вводятся с помощью вызова contract функции stdlib, которая предоставляет область действия DSL:

fun String?.isNullOrEmpty(): Boolean {
    contract {
        returns(false) implies (this@isNullOrEmpty != null)
    }
    return this == null || isEmpty()
}

См. подробности синтаксиса и заметку о совместимости в KEEP.

Переменная для объекта в выражении when

В Kotlin 1.3 теперь можно поместить when объект в переменную:

fun Request.getBody() =
        when (val response = executeRequest()) {
            is Success -> response.body
            is HttpError -> throw HttpException(response.status)
        }

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

@JvmStatic и @JvmField в компаньонах интерфейсов

В Kotlin 1.3 можно пометить члены companion объекта интерфейсов аннотациями @JvmStatic и @JvmField. В файле class-файла такие члены будут подняты до соответствующего интерфейса и помечены как static.

Например, следующий код Kotlin:

interface Foo {
    companion object {
        @JvmField
        val answer: Int = 42

        @JvmStatic
        fun sayHello() {
            println("Hello, world!")
        }
    }
}

Эквивалентен следующему коду Java:

interface Foo {
    public static int answer = 42;
    public static void sayHello() {
        // ...
    }
}

Вложенные объявления в аннотационных классах

В Kotlin 1.3 аннотации могут содержать вложенные классы, интерфейсы, объекты и компаньоны:

annotation class Foo {
    enum class Direction { UP, DOWN, LEFT, RIGHT }
    
    annotation class Bar

    companion object {
        fun foo(): Int = 42
        val bar: Int = 42
    }
}

Главная функция без параметров

По соглашению, точкой входа в Kotlin-программе является функция с сигнатурой, подобной main(args: Array<String>), где args представляют аргументы командной строки, переданные программе. Однако не каждое приложение поддерживает аргументы командной строки, поэтому этот параметр часто оказывается неиспользуемым.

Kotlin 1.3 ввёл более простой вид main, который не принимает параметров. Теперь "Hello, World" на Kotlin короче на 19 символов!

fun main() {
    println("Hello, world!")
}

Функции с большой арностью

В Kotlin функциональные типы представлены как обобщённые классы, принимающие разное количество параметров: Function0<R>, Function1<P0, R>, Function2<P0, P1, R>, ... Этот подход имеет проблему, заключающуюся в том, что этот список конечен, и в настоящее время заканчивается Function22.

Kotlin 1.3 снимает это ограничение и добавляет поддержку функций с большей арностью:

fun trueEnterpriseComesToKotlin(block: (Any, Any, ... /* 42 more */, Any) -> Any) {
    block(Any(), Any(), ..., Any())
}

Режим поэтапного улучшения

Kotlin уделяет большое внимание стабильности и обратной совместимости кода: политика совместимости Kotlin гласит, что изменения, нарушающие обратную совместимость (например, изменение, которое делает код, который раньше компилировался, теперь не компилируется), могут быть внесены только в крупных выпусках (1.2, 1.3 и т.д.).

Мы считаем, что многие пользователи могли бы использовать более быстрый цикл, где критические исправления ошибок компилятора появляются немедленно, делая код более безопасным и правильным. Поэтому Kotlin 1.3 вводит режим компилятора поэтапного улучшения, который можно включить, передав аргумент -progressive компилятору.

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

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

  • Они лишь делают код более безопасным в некотором смысле — например, некоторые некорректные интеллектуальные преобразования типов могут быть запрещены, поведение сгенерированного кода может быть изменено для повышения предсказуемости/стабильности и так далее.

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

Встроенные классы

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

Kotlin 1.3 вводит новый тип объявления — inline class. Встроенные классы можно рассматривать как ограниченную версию обычных классов, в частности, встроенные классы должны иметь ровно одно свойство:

inline class Name(val s: String)

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

inline class Name(val s: String)
//sampleStart
fun main() {
    // In the next line no constructor call happens, and
    // at the runtime 'name' contains just string "Kotlin"
    val name = Name("Kotlin")
    println(name.s) 
}
//sampleEnd

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

END_OF_DOCUMENT_MARKER

Целочисленные без знака

Целочисленные без знака находятся в стадии Бета. Их реализация почти стабильна, но в будущем могут потребоваться шаги по миграции. Мы сделаем все возможное, чтобы свести к минимуму изменения, которые вам придется внести.

Kotlin 1.3 вводит типы целочисленных значений без знака:

  • kotlin.UByte: целочисленное значение без знака 8 бит, значения от 0 до 255

  • kotlin.UShort: целочисленное значение без знака 16 бит, значения от 0 до 65535

  • kotlin.UInt: целочисленное значение без знака 32 бит, значения от 0 до 2^32 - 1

  • kotlin.ULong: целочисленное значение без знака 64 бит, значения от 0 до 2^64 - 1

Большинство функций для знаковых типов также поддерживаются для их аналогов без знака:

fun main() {
//sampleStart
// You can define unsigned types using literal suffixes
val uint = 42u 
val ulong = 42uL
val ubyte: UByte = 255u

// You can convert signed types to unsigned and vice versa via stdlib extensions:
val int = uint.toInt()
val byte = ubyte.toByte()
val ulong2 = byte.toULong()

// Unsigned types support similar operators:
val x = 20u + 22u
val y = 1u shl 8
val z = "128".toUByte()
val range = 1u..5u
//sampleEnd
println("ubyte: $ubyte, byte: $byte, ulong2: $ulong2")
println("x: $x, y: $y, z: $z, range: $range")
}

Подробности см. в справочнике.

@JvmDefault

@JvmDefault находится в стадии Экспериментальной. Он может быть удалён или изменён в любое время. Используйте только для оценочных целей. Мы будем рады вашим отзывам в YouTrack.

Kotlin поддерживает широкий спектр версий Java, включая Java 6 и Java 7, где методы по умолчанию в интерфейсах не разрешены. Для удобства компилятор Kotlin обходит это ограничение, но это решение несовместимо с default методами, введёнными в Java 8.

Это может быть проблемой для межплатформенной совместимости с Java, поэтому Kotlin 1.3 вводит аннотацию @JvmDefault. Методы, помеченные этой аннотацией, будут генерироваться как default методы для JVM:

interface Foo {
    // Will be generated as 'default' method
    @JvmDefault
    fun foo(): Int = 42
}

Предупреждение! Пометка вашего API аннотацией @JvmDefault имеет серьёзные последствия для бинарной совместимости. Убедитесь, что вы внимательно прочитали страницу справки перед использованием @JvmDefault в производстве.

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

Многоплатформенный генератор случайных чисел

До Kotlin 1.3 не было единого способа генерации случайных чисел на всех платформах — нам приходилось прибегать к платформно-специфичным решениям, таким как java.util.Random на JVM. Этот релиз исправляет эту проблему, представив класс kotlin.random.Random, который доступен на всех платформах:

import kotlin.random.Random

fun main() {
//sampleStart
    val number = Random.nextInt(42)  // number is in range [0, limit)
    println(number)
//sampleEnd
}

Расширения isNullOrEmpty и orEmpty

isNullOrEmpty и orEmpty расширения для некоторых типов уже присутствуют в стандартной библиотеке. Первый возвращает true если получатель является null или пустым, а второй возвращает пустой экземпляр, если получатель null. Kotlin 1.3 предоставляет аналогичные расширения для коллекций, карт и массивов объектов.

Копирование элементов между двумя существующими массивами

Функции array.copyInto(targetArray, targetOffset, startIndex, endIndex) для существующих типов массивов, включая массивы без знака, упрощают реализацию контейнеров на основе массивов на чистом Kotlin.

fun main() {
//sampleStart
    val sourceArr = arrayOf("k", "o", "t", "l", "i", "n")
    val targetArr = sourceArr.copyInto(arrayOfNulls<String>(6), 3, startIndex = 3, endIndex = 6)
    println(targetArr.contentToString())
    
    sourceArr.copyInto(targetArr, startIndex = 0, endIndex = 3)
    println(targetArr.contentToString())
//sampleEnd
}

associateWith

Довольно распространённая ситуация — список ключей и желание создать карту, связав каждый из этих ключей с каким-либо значением. Это было возможно и раньше с помощью функции associate { it to getValue(it) }, но теперь мы представляем более эффективный и удобный для изучения вариант: keys.associateWith { getValue(it) }.

fun main() {
//sampleStart
    val keys = 'a'..'f'
    val map = keys.associateWith { it.toString().repeat(5).capitalize() }
    map.forEach { println(it) }
//sampleEnd
}

Функции ifEmpty и ifBlank

Коллекции, карты, массивы объектов, последовательности символов и последовательности теперь имеют функцию ifEmpty, которая позволяет указать значение по умолчанию, которое будет использоваться вместо получателя, если он пуст:

fun main() {
//sampleStart
    fun printAllUppercase(data: List<String>) {
        val result = data
        .filter { it.all { c -> c.isUpperCase() } }
            .ifEmpty { listOf("<no uppercase>") }
        result.forEach { println(it) }
    }
    
    printAllUppercase(listOf("foo", "Bar"))
    printAllUppercase(listOf("FOO", "BAR"))
//sampleEnd
}

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

fun main() {
//sampleStart
    val s = "    \n"
    println(s.ifBlank { "<blank>" })
    println(s.ifBlank { null })
//sampleEnd
}

Запечатанные классы в рефлексии

Мы добавили новую API к kotlin-reflect, которая может использоваться для перечисления всех прямых подтипов sealed класса, а именно KClass.sealedSubclasses.

Небольшие изменения

  • Boolean тип теперь имеет компаньон.

  • Any?.hashCode() расширение, которое возвращает 0 для null.

  • Char теперь предоставляет MIN_VALUE и MAX_VALUE константы.

  • SIZE_BYTES и SIZE_BITS константы в компаньонах примитивных типов.

Инструменты

Поддержка стиля кода в IDE

Kotlin 1.3 добавляет поддержку рекомендуемого стиля кода в IntelliJ IDEA. См. эту страницу для руководства по миграции.

kotlinx.serialization

kotlinx.serialization — библиотека, которая предоставляет поддержку многоплатформенной сериализации/десериализации объектов в Kotlin. Раньше это был отдельный проект, но с Kotlin 1.3 он поставляется вместе с дистрибутивом компилятора Kotlin наравне с другими плагинами компилятора. Основное отличие в том, что вам не нужно вручную следить за совместимостью плагина Serialization IDE Plugin с версией плагина Kotlin IDE, которую вы используете: теперь плагин Kotlin IDE уже включает сериализацию!

См. здесь подробности.

Несмотря на то, что kotlinx.serialization теперь поставляется вместе с дистрибутивом Kotlin Compiler, в Kotlin 1.3 она всё ещё считается экспериментальной функцией.

Обновление скриптов

Скриптинг находится в стадии Экспериментальной. Он может быть удалён или изменён в любое время. Используйте только для оценочных целей. Мы будем рады вашим отзывам в YouTrack.

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

Дополнительную информацию см. в KEEP-75.

Поддержка Scratch-файлов

Kotlin 1.3 вводит поддержку исполняемых Kotlin scratch-файлов. Scratch-файл — это файл kotlin-скрипта с расширением .kts, который можно запустить и получить результаты выполнения непосредственно в редакторе.

Для получения дополнительной информации см. общую документацию по Scratch-файлам.

Последнее изменение: 25 мая 2023 г.
Что нового в Kotlin 1.4.0 Что нового в Kotlin 1.2

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

Spec-Zone.ru

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