Что нового в 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