Spec-Zone.ru › Kotlin 1.4

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

Выпуск корутин

После длительных и обширных тестов корутины теперь выпущены! Это означает, что с 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 compiler:
    // "if this function returns successfully, then passed 'condition' is true"
    contract { returns() implies condition }
    if (!condition) throw IllegalArgumentException(...)
}

fun foo(s: String?) {
    require(s is String)
    // s is smartcasted to 'String' here, because otherwise
    // 'require' would have throw an exception
}
  • Улучшение анализа инициализации переменных при наличии функций высшего порядка:
fun synchronize(lock: Any?, block: () -> Unit) {
    // It tells 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, smartcasted 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. В файле класса такие члены будут подняты до соответствующего интерфейса и помечены как 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
    }
}

Функция main без параметров

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

Kotlin 1.3 ввёл более простой вид main без параметров. Теперь «Привет, мир» на 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 компилятору.

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

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

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

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

Встроенные классы доступны только начиная с Kotlin 1.3 и в настоящее время находятся на стадии Альфа. Подробности см. в справке.

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

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

Беззнаковые целые числа

Беззнаковые целые числа доступны только начиная с Kotlin 1.3 и в настоящее время находятся на стадии Бета. Подробности см. в справке.

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

  • kotlin.UByte: беззнаковое целое 8-битное число, значения от 0 до 255
  • kotlin.UShort: беззнаковое целое 16-битное число, значения от 0 до 65535
  • kotlin.UInt: беззнаковое целое 32-битное число, значения от 0 до 232 - 1
  • kotlin.ULong: беззнаковое целое 64-битное число, значения от 0 до 264 - 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 доступна только начиная с Kotlin 1.3 и в настоящее время является экспериментальной. Подробности см. на странице справки.

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
}

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

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

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

До 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
}

Расширения isNullOrBlank/orEmpty

Расширения isNullOrEmpty и orEmpty для некоторых типов уже присутствуют в stdlib. Первое возвращает 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 добавляет поддержку рекомендуемого стиля кода в IDE. См. эту страницу для рекомендаций по миграции.

kotlinx.serialization

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

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

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

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

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

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

Дополнительные сведения см. в KEEP-75.

Поддержка черновиков

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

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

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

Spec-Zone.ru

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