Что нового в 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. В файле класса такие члены будут подняты в соответствующий интерфейс и помечены как 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 до 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 для некоторых типов уже присутствуют в стандартной библиотеке. Первое возвращает 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 уже включает сериализацию!
См. подробности здесь.
Обновление скриптов
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