Что нового в 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 теперь можно получить доступ к объекту <%%CODE_BLOCK_9%%%> в переменную:
fun Request.getBody() =
when (val response = executeRequest()) {
is Success -> response.body
is HttpError -> throw HttpException(response.status)
}
Хотя это уже было возможно сделать с использованием объекта <%%CODE_BLOCK_11%%>, <%%CODE_BLOCK_12%%%> в <%%CODE_BLOCK_13%%%> имеет соответствующий объём, ограниченный телом <%%CODE_BLOCK_14%%>, предотвращая загрязнение пространства имён. Полная документация по <%%CODE_BLOCK_15%%%> здесь.
@JvmStatic и @JvmField в компаньонах интерфейсов
В Kotlin 1.3 можно помечать члены <%%CODE_BLOCK_16%%%> объекта интерфейсов аннотациями <%%CODE_BLOCK_17%%%> и <%%CODE_BLOCK_18%%>. В файле с байткодом такие члены будут перенесены в соответствующий интерфейс и помечены как <%%CODE_BLOCK_19%%>.
Например, следующий код 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 является функция с сигнатурой, подобной <%%CODE_BLOCK_23%%>, где <%%CODE_BLOCK_24%%> представляют аргументы командной строки, передаваемые в программу. Однако не каждое приложение поддерживает аргументы командной строки, поэтому этот параметр часто не используется.
Kotlin 1.3 ввёл более простой вариант <%%CODE_BLOCK_25%%>, который не принимает параметров. Теперь "Hello, World" на Kotlin короче на 19 символов!
fun main() {
println("Hello, world!")
}
Функции с большим числом аргументов
В Kotlin функциональные типы представляются как классы дженериков, принимающие разное число параметров: <%%CODE_BLOCK_27%%>, <%%CODE_BLOCK_28%%>, <%%CODE_BLOCK_29%%>, ... Такой подход имеет проблему, состоящую в том, что этот список конечный, и в настоящее время заканчивается <%%CODE_BLOCK_30%%%.
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 вводит постепенный режим компилятора, который можно включить, передав аргумент <%%CODE_BLOCK_32%%> компилятору.
В режиме постепенного компилятора некоторые исправления в семантике языка могут появиться немедленно. Все эти исправления обладают двумя важными свойствами:
Они сохраняют обратную совместимость исходного кода со старыми компиляторами, что означает, что весь код, который компилируется постепенным компилятором, будет успешно компилироваться и обычным компилятором.
Они только делают код более безопасным в некотором смысле — например, некоторые некорректные интеллектуальные преобразования типов могут быть запрещены, поведение сгенерированного кода может быть изменено для большей предсказуемости/стабильности и так далее.
Включение постепенного режима может потребовать от вас переписывания некоторых частей кода, но это не должно быть слишком обременительно — все исправления, включённые в постепенный режим, тщательно выбираются, проверяются и снабжены инструментальными средствами для миграции. Мы ожидаем, что постепенный режим будет полезен для всех активно поддерживаемых кодовых баз, которые быстро обновляются до последних версий языка.
Встроенные классы
Kotlin 1.3 вводит новый тип объявления — <%%CODE_BLOCK_33%%>. Встроенные классы можно рассматривать как ограниченную версию обычных классов, в частности, встроенные классы должны иметь ровно одно свойство:
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 до 232 - 1kotlin.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
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
}
Стандартная библиотека
Многоплатформенный генератор случайных чисел
До 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 добавляет поддержку рекомендуемого стиля кода в IntelliJ IDEA. См. эту страницу для руководства по миграции.
kotlinx.serialization
kotlinx.serialization — это библиотека, предоставляющая поддержку многоплатформенного (де)сериализации объектов в Kotlin. Раньше это был отдельный проект, но с Kotlin 1.3 он поставляется с дистрибутивом компилятора Kotlin наравне с другими плагинами компилятора. Основное отличие заключается в том, что вам не нужно вручную следить за тем, чтобы плагин Serialization IDE был совместим с версией плагина Kotlin IDE, которую вы используете: теперь плагин Kotlin IDE уже включает сериализацию!
См. подробности здесь.
Обновление скриптового API
Kotlin 1.3 продолжает развивать и улучшать API скриптирования, добавляя экспериментальную поддержку настройки скриптов, например, добавления внешних свойств, предоставления статических или динамических зависимостей и т. д.
Дополнительные сведения см. в KEEP-75.
Поддержка эскизов
Kotlin 1.3 вводит поддержку исполняемых Kotlin-файлов-эскизов. Файл-эскиз — это Kotlin-скрипт-файл с расширением .kts, который вы можете запустить и получить результаты вычисления непосредственно в редакторе.
Для получения подробной информации см. общую документацию по эскизам.
© 2010–2022 JetBrains s.r.o. and Kotlin Programming Language contributors
Licensed under the Apache License, Version 2.0.
https://kotlinlang.org/docs/whatsnew13.html