Что нового в 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 компилятору.
В режиме поэтапного улучшения некоторые исправления семантики языка могут появиться немедленно. Все эти исправления обладают двумя важными свойствами:
Они сохраняют обратную совместимость исходного кода со старыми компиляторами, что означает, что весь код, который можно скомпилировать с прогрессивным компилятором, будет без проблем компилироваться и с обычным.
Они лишь делают код более безопасным в некотором смысле — например, некоторые некорректные интеллектуальные преобразования типов могут быть запрещены, поведение сгенерированного кода может быть изменено для повышения предсказуемости/стабильности и так далее.
Включение режима поэтапного улучшения может потребовать переписывания части вашего кода, но это не должно быть слишком сложно — все исправления, включенные в режиме поэтапного улучшения, тщательно отобраны, проверены и снабжены инструментальными средствами для миграции. Мы ожидаем, что режим поэтапного улучшения станет хорошим выбором для всех активно поддерживаемых кодовых баз, которые быстро обновляются до последних версий языка.
Встроенные классы
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. Методы, помеченные этой аннотацией, будут генерироваться как 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
}
Расширения 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 уже включает сериализацию!
См. здесь подробности.
Обновление скриптов
Kotlin 1.3 продолжает развивать и улучшать API скриптов, вводя некоторые экспериментальные возможности по настройке скриптов, такие как добавление внешних свойств, предоставление статических или динамических зависимостей и т. д.
Дополнительную информацию см. в KEEP-75.
Поддержка Scratch-файлов
Kotlin 1.3 вводит поддержку исполняемых Kotlin scratch-файлов. Scratch-файл — это файл kotlin-скрипта с расширением .kts, который можно запустить и получить результаты выполнения непосредственно в редакторе.
Для получения дополнительной информации см. общую документацию по Scratch-файлам.
© 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