Что нового в Kotlin 1.3
Выпущена: 29 октября 2018 г.
Релиз корутин
После длительного и масштабного тестирования корутины наконец выпущены! Это означает, что начиная с Kotlin 1.3 поддержка языка и API полностью стабильны. Ознакомьтесь с новой страницей обзора корутин.
В Kotlin 1.3 появилась возможность создавать ссылки на вызываемые suspend-функции, а также поддержка корутин в 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 уже использует контракты, что улучшает описанные выше виды анализа. Эта часть контрактов стабильна, то есть воспользоваться улучшенным анализом можно уже сейчас без дополнительных opt-in:
//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 не проверяет контракты, поэтому программист несёт ответственность за то, чтобы контракты были корректными и надёжными.
Пользовательские контракты объявляются вызовом функции stdlib contract, которая предоставляет область видимости DSL:
fun String?.isNullOrEmpty(): Boolean {
contract {
returns(false) implies (this@isNullOrEmpty != null)
}
return this == null || isEmpty()
}
Подробное описание синтаксиса и уведомление о совместимости см. в документе KEEP.
Сохранение subject выражения when в переменную
В Kotlin 1.3 subject выражения 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 без параметров. Теперь фраза «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.
В прогрессивном режиме некоторые исправления семантики языка могут появляться немедленно. Все эти исправления обладают двумя важными свойствами:
Они сохраняют обратную совместимость исходного кода со старыми компиляторами: любой код, компилируемый прогрессивным компилятором, будет успешно скомпилирован и обычным компилятором.
Они лишь делают код в некотором смысле безопаснее: например, могут запретить некоторые ненадёжные умные приведения типов, изменить поведение сгенерированного кода, сделав его более предсказуемым и стабильным, и т. д.
Включение прогрессивного режима может потребовать переписать часть кода, но объём изменений не должен быть большим: все исправления, включённые в прогрессивный режим, тщательно отобраны и проверены, а для их применения предусмотрены средства миграции. Мы считаем, что прогрессивный режим хорошо подойдёт для активно поддерживаемых кодовых баз, которые быстро обновляются до последних версий языка.
Встраиваемые классы
В 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.UByte: беззнаковое 8-битное целое число в диапазоне от 0 до 255kotlin.UShort: беззнаковое 16-битное целое число в диапазоне от 0 до 65535kotlin.UInt: беззнаковое 32-битное целое число в диапазоне от 0 до 2^32 - 1kotlin.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
Kotlin ориентирован на широкий спектр версий Java, включая Java 6 и Java 7, в которых интерфейсы не могут содержать методы по умолчанию. Чтобы упростить работу, компилятор Kotlin обходит это ограничение, однако такой обход несовместим с методами default, появившимися в Java 8.
Это может создавать проблемы при взаимодействии с Java, поэтому в Kotlin 1.3 появилась аннотация @JvmDefault. Методы с этой аннотацией будут генерироваться для JVM как методы default:
interface Foo {
// Will be generated as 'default' method
@JvmDefault
fun foo(): Int = 42
}
Стандартная библиотека
Мультиплатформенный генератор случайных чисел
До 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
В stdlib уже есть расширения 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
}
Запечатанные классы в рефлексии
В kotlin-reflect добавлен новый API для перечисления всех непосредственных подклассов 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 с используемой версией плагина Kotlin IDE: теперь плагин Kotlin IDE уже включает сериализацию!
Подробности см. здесь.
Обновление скриптов
В Kotlin 1.3 продолжается развитие и совершенствование API для скриптов: добавлена экспериментальная поддержка настройки скриптов, например добавления внешних свойств, задания статических или динамических зависимостей и т. д.
Дополнительную информацию см. в документе KEEP-75.
Поддержка черновых файлов
В Kotlin 1.3 добавлена поддержка запускаемых Kotlin-черновых файлов. Черновой файл — это файл скрипта Kotlin с расширением .kts, который можно запустить и сразу увидеть результаты выполнения в редакторе.
Подробности см. в общей документации по черновым файлам.
© 2010–2026 JetBrains s.r.o. and Kotlin Programming Language contributors
Licensed under the Apache License, Version 2.0.
https://kotlinlang.org/docs/whatsnew13.html