Что нового в Kotlin 1.1
Выпуск: 15 февраля 2016 г.
Содержание
JavaScript
Начиная с Kotlin 1.1, целевая платформа JavaScript больше не считается экспериментальной. Поддерживаются все возможности языка, а для интеграции со средой фронтенд-разработки доступно множество новых инструментов. Более подробный список изменений см. ниже.
Коррутины (экспериментальная функция)
Ключевая новая возможность Kotlin 1.1 — корутины, обеспечивающие поддержку async/await, yield и подобных шаблонов программирования. Ключевая особенность Kotlin заключается в том, что реализация выполнения корутин входит в состав библиотек, а не языка, поэтому вы не привязаны к какой-либо конкретной парадигме программирования или библиотеке параллелизма.
Корутина — это, по сути, облегчённый поток, выполнение которого можно приостановить и возобновить позже. Поддержка корутин реализована с помощью приостанавливаемых функций: вызов такой функции может приостановить корутину, а для запуска новой корутины обычно используются анонимные приостанавливаемые функции (то есть приостанавливаемые лямбда-выражения).
Рассмотрим async/await, реализованный во внешней библиотеке kotlinx.coroutines:
// runs the code in the background thread pool
fun asyncOverlay() = async(CommonPool) {
// start two async operations
val original = asyncLoadImage("original")
val overlay = asyncLoadImage("overlay")
// and then apply overlay to both results
applyOverlay(original.await(), overlay.await())
}
// launches new coroutine in UI context
launch(UI) {
// wait for async overlay to complete
val image = asyncOverlay().await()
// and then show it in UI
showImage(image)
}
Здесь async { ... } запускает корутину, и при использовании await() выполнение корутины приостанавливается на время выполнения ожидаемой операции, а после её завершения возобновляется (возможно, в другом потоке).
Стандартная библиотека использует корутины для поддержки последовательностей с ленивой генерацией с помощью функций yield и yieldAll. В такой последовательности блок кода, возвращающий элементы, приостанавливается после получения каждого элемента и возобновляется, когда запрашивается следующий. Вот пример:
import kotlin.coroutines.experimental.*
fun main(args: Array<String>) {
val seq = buildSequence {
for (i in 1..5) {
// yield a square of i
yield(i * i)
}
// yield a range
yieldAll(26..28)
}
// print the sequence
println(seq.toList())
}
Запустите приведённый выше код, чтобы увидеть результат. Можете изменить его и запустить ещё раз!
Дополнительные сведения см. в документации по корутинам и руководстве.
Обратите внимание: сейчас корутины считаются экспериментальной функцией. Это означает, что команда Kotlin не гарантирует обратную совместимость этой функции после финального выпуска версии 1.1.
Другие возможности языка
Псевдонимы типов
Псевдоним типа позволяет задать альтернативное имя для существующего типа. Это особенно полезно для обобщённых типов, таких как коллекции, а также для типов функций. Вот пример:
//sampleStart
typealias OscarWinners = Map<String, String>
fun countLaLaLand(oscarWinners: OscarWinners) =
oscarWinners.count { it.value.contains("La La Land") }
// Note that the type names (initial and the type alias) are interchangeable:
fun checkLaLaLandIsTheBestMovie(oscarWinners: Map<String, String>) =
oscarWinners["Best picture"] == "La La Land"
//sampleEnd
fun oscarWinners(): OscarWinners {
return mapOf(
"Best song" to "City of Stars (La La Land)",
"Best actress" to "Emma Stone (La La Land)",
"Best picture" to "Moonlight" /* ... */)
}
fun main(args: Array<String>) {
val oscarWinners = oscarWinners()
val laLaLandAwards = countLaLaLand(oscarWinners)
println("LaLaLandAwards = $laLaLandAwards (in our small example), but actually it's 6.")
val laLaLandIsTheBestMovie = checkLaLaLandIsTheBestMovie(oscarWinners)
println("LaLaLandIsTheBestMovie = $laLaLandIsTheBestMovie")
}
Подробнее см. в документации по псевдонимам типов и в предложении KEEP.
Привязанные ссылки на вызываемые объекты
Теперь можно использовать оператор ::, чтобы получить ссылку на член, указывающую на метод или свойство конкретного экземпляра объекта. Раньше это можно было выразить только с помощью лямбда-выражения. Вот пример:
//sampleStart
val numberRegex = "\\d+".toRegex()
val numbers = listOf("abc", "123", "456").filter(numberRegex::matches)
//sampleEnd
fun main(args: Array<String>) {
println("Result is $numbers")
}
Подробнее см. в документации и в предложении KEEP.
Закрытые и data-классы
В Kotlin 1.1 сняты некоторые ограничения на закрытые и data-классы, существовавшие в Kotlin 1.0. Теперь подклассы закрытого класса верхнего уровня можно объявлять на верхнем уровне в том же файле, а не только как вложенные классы закрытого класса. Data-классы теперь могут наследоваться от других классов. Это позволяет просто и наглядно определять иерархию классов выражений:
//sampleStart
sealed class Expr
data class Const(val number: Double) : Expr()
data class Sum(val e1: Expr, val e2: Expr) : Expr()
object NotANumber : Expr()
fun eval(expr: Expr): Double = when (expr) {
is Const -> expr.number
is Sum -> eval(expr.e1) + eval(expr.e2)
NotANumber -> Double.NaN
}
val e = eval(Sum(Const(1.0), Const(2.0)))
//sampleEnd
fun main(args: Array<String>) {
println("e is $e") // 3.0
}
Подробнее см. в документации по закрытым классам или предложениях KEEP о закрытых классах и data-классах.
Деструктуризация в лямбда-выражениях
Теперь для распаковки аргументов, передаваемых лямбда-выражению, можно использовать синтаксис деструктурирующего объявления. Вот пример:
fun main(args: Array<String>) {
//sampleStart
val map = mapOf(1 to "one", 2 to "two")
// before
println(map.mapValues { entry ->
val (key, value) = entry
"$key -> $value!"
})
// now
println(map.mapValues { (key, value) -> "$key -> $value!" })
//sampleEnd
}
Подробнее см. в документации по деструктурирующим объявлениям и предложении KEEP.
Подчёркивания для неиспользуемых параметров
В лямбда-выражении с несколькими параметрами можно заменить имена неиспользуемых параметров символом _:
fun main(args: Array<String>) {
val map = mapOf(1 to "one", 2 to "two")
//sampleStart
map.forEach { _, value -> println("$value!") }
//sampleEnd
}
Это работает и в деструктурирующих объявлениях:
data class Result(val value: Any, val status: String)
fun getResult() = Result(42, "ok").also { println("getResult() returns $it") }
fun main(args: Array<String>) {
//sampleStart
val (_, status) = getResult()
//sampleEnd
println("status is '$status'")
}
Подробнее см. в предложении KEEP.
Подчёркивания в числовых литералах
Как и в Java 8, теперь в Kotlin можно использовать подчёркивания в числовых литералах, чтобы разделять группы цифр:
//sampleStart
val oneMillion = 1_000_000
val hexBytes = 0xFF_EC_DE_5E
val bytes = 0b11010010_01101001_10010100_10010010
//sampleEnd
fun main(args: Array<String>) {
println(oneMillion)
println(hexBytes.toString(16))
println(bytes.toString(2))
}
Подробнее см. в предложении KEEP.
Сокращённый синтаксис свойств
Для свойств, геттер которых определён в виде тела-выражения, теперь можно не указывать тип свойства:
//sampleStart
data class Person(val name: String, val age: Int) {
val isAdult get() = age >= 20 // Property type inferred to be 'Boolean'
}
//sampleEnd
fun main(args: Array<String>) {
val akari = Person("Akari", 26)
println("$akari.isAdult = ${akari.isAdult}")
}
Встраиваемые аксессоры свойств
Теперь можно помечать аксессоры свойств модификатором inline, если у свойств нет поля хранения. Такие аксессоры компилируются так же, как встраиваемые функции.
//sampleStart
public val <T> List<T>.lastIndex: Int
inline get() = this.size - 1
//sampleEnd
fun main(args: Array<String>) {
val list = listOf('a', 'b')
// the getter will be inlined
println("Last index of $list is ${list.lastIndex}")
}
Можно также пометить как inline всё свойство — тогда модификатор будет применён к обоим аксессорам.
Подробнее см. в документации по встраиваемым функциям и предложении KEEP.
Локальные делегированные свойства
Теперь синтаксис делегированных свойств можно использовать с локальными переменными. Например, так можно объявить локальную переменную с ленивым вычислением:
import java.util.Random
fun needAnswer() = Random().nextBoolean()
fun main(args: Array<String>) {
//sampleStart
val answer by lazy {
println("Calculating the answer...")
42
}
if (needAnswer()) { // returns the random value
println("The answer is $answer.") // answer is calculated at this point
}
else {
println("Sometimes no answer is the answer...")
}
//sampleEnd
}
Подробнее см. в предложении KEEP.
Перехват привязки делегированного свойства
Для делегированных свойств теперь можно перехватывать привязку делегата к свойству с помощью оператора provideDelegate. Например, если перед привязкой нужно проверить имя свойства, можно написать следующее:
class ResourceLoader<T>(id: ResourceID<T>) {
operator fun provideDelegate(thisRef: MyUI, prop: KProperty<*>): ReadOnlyProperty<MyUI, T> {
checkProperty(thisRef, prop.name)
... // property creation
}
private fun checkProperty(thisRef: MyUI, name: String) { ... }
}
fun <T> bindResource(id: ResourceID<T>): ResourceLoader<T> { ... }
class MyUI {
val image by bindResource(ResourceID.image_id)
val text by bindResource(ResourceID.text_id)
}
Метод provideDelegate будет вызван для каждого свойства при создании экземпляра MyUI и сразу сможет выполнить необходимую проверку.
Подробнее см. в документации по делегированным свойствам.
Обобщённый доступ к значениям перечисления
Теперь значения класса-перечисления можно перечислять обобщённым способом.
//sampleStart
enum class RGB { RED, GREEN, BLUE }
inline fun <reified T : Enum<T>> printAllValues() {
print(enumValues<T>().joinToString { it.name })
}
//sampleEnd
fun main(args: Array<String>) {
printAllValues<RGB>() // prints RED, GREEN, BLUE
}
Управление областью видимости неявных получателей в DSL
Аннотация @DslMarker позволяет ограничить использование получателей из внешних областей видимости в контексте DSL. Рассмотрим классический пример построителя HTML:
table {
tr {
td { + "Text" }
}
}
В Kotlin 1.0 код в лямбда-выражении, переданном в td, имеет доступ к трём неявным получателям: переданному в table, в tr и в td. Это позволяет вызывать методы, не имеющие смысла в данном контексте: например, вызывать tr внутри td и тем самым помещать тег <tr> в <td>.
В Kotlin 1.1 это можно ограничить: внутри лямбда-выражения, переданного в td, будут доступны только методы, определённые для неявного получателя td. Для этого нужно определить аннотацию с мета-аннотацией @DslMarker и применить её к базовому классу классов тегов.
Подробнее см. в документации по типобезопасным построителям и предложении KEEP.
Оператор rem
Оператор mod теперь объявлен устаревшим; вместо него используется rem. Обоснование см. в этой задаче.
Стандартная библиотека
Преобразование строк в числа
Для класса String добавлено несколько расширений, преобразующих строку в число без выбрасывания исключения при некорректном значении: String.toIntOrNull(): Int?, String.toDoubleOrNull(): Double? и т. д.
val port = System.getenv("PORT")?.toIntOrNull() ?: 80
Также для функций преобразования целых чисел, таких как Int.toString(), String.toInt(), String.toIntOrNull(), добавлена перегрузка с параметром radix, позволяющим указать основание системы счисления (от 2 до 36).
onEach()
onEach — небольшая, но полезная функция-расширение для коллекций и последовательностей. Она позволяет выполнять некоторое действие, возможно, с побочными эффектами, над каждым элементом коллекции или последовательности в цепочке операций. Для итерируемых объектов она ведёт себя как forEach, но также возвращает исходный итерируемый объект. Для последовательностей она возвращает обёртывающую последовательность, которая лениво применяет заданное действие по мере перебора элементов.
inputDir.walk()
.filter { it.isFile && it.name.endsWith(".txt") }
.onEach { println("Moving $it to $outputDir") }
.forEach { moveFile(it, File(outputDir, it.toRelativeString(inputDir))) }
also(), takeIf() и takeUnless()
Это три функции-расширения общего назначения, применимые к любому получателю.
also похожа на apply: она принимает получателя, выполняет над ним некоторое действие и возвращает его. Разница в том, что в блоке внутри apply получатель доступен как this, а в блоке внутри also — как it (при желании ему можно задать другое имя). Это удобно, когда не нужно затенять this из внешней области видимости:
class Block {
lateinit var content: String
}
//sampleStart
fun Block.copy() = Block().also {
it.content = this.content
}
//sampleEnd
// using 'apply' instead
fun Block.copy1() = Block().apply {
this.content = this@copy1.content
}
fun main(args: Array<String>) {
val block = Block().apply { content = "content" }
val copy = block.copy()
println("Testing the content was copied:")
println(block.content == copy.content)
}
takeIf похожа на filter, но применяется к одному значению. Она проверяет, удовлетворяет ли получатель условию, и возвращает его, если условие выполнено, или null, если нет. В сочетании с оператором Элвиса (?:) и ранним возвратом это позволяет писать конструкции вроде:
val outDirFile = File(outputDir.path).takeIf { it.exists() } ?: return false
// do something with existing outDirFile
fun main(args: Array<String>) {
val input = "Kotlin"
val keyword = "in"
//sampleStart
val index = input.indexOf(keyword).takeIf { it >= 0 } ?: error("keyword not found")
// do something with index of keyword in input string, given that it's found
//sampleEnd
println("'$keyword' was found in '$input'")
println(input)
println(" ".repeat(index) + "^")
}
takeUnless — это то же, что takeIf, но с инвертированным условием. Она возвращает получателя, если он не удовлетворяет условию, и null в противном случае. Таким образом, один из приведённых выше примеров можно переписать с помощью takeUnless следующим образом:
val index = input.indexOf(keyword).takeUnless { it < 0 } ?: error("keyword not found")
Эта функция также удобна, когда вместо лямбда-выражения используется ссылка на вызываемый объект:
private fun testTakeUnless(string: String) {
//sampleStart
val result = string.takeUnless(String::isEmpty)
//sampleEnd
println("string = \"$string\"; result = \"$result\"")
}
fun main(args: Array<String>) {
testTakeUnless("")
testTakeUnless("abc")
}
groupingBy()
Этот API можно использовать для группировки коллекции по ключу с одновременным свёртыванием каждой группы. Например, с его помощью можно подсчитать количество слов, начинающихся с каждой буквы:
fun main(args: Array<String>) {
val words = "one two three four five six seven eight nine ten".split(' ')
//sampleStart
val frequencies = words.groupingBy { it.first() }.eachCount()
//sampleEnd
println("Counting first letters: $frequencies.")
// The alternative way that uses 'groupBy' and 'mapValues' creates an intermediate map,
// while 'groupingBy' way counts on the fly.
val groupBy = words.groupBy { it.first() }.mapValues { (_, list) -> list.size }
println("Comparing the result with using 'groupBy': ${groupBy == frequencies}.")
}
Map.toMap() и Map.toMutableMap()
Эти функции позволяют легко копировать отображения:
class ImmutablePropertyBag(map: Map<String, Any>) {
private val mapCopy = map.toMap()
}
Map.minus(key)
Оператор plus позволяет добавлять пары «ключ-значение» в доступное только для чтения отображение, создавая новое отображение. Однако простой способ выполнить обратное действие — удалить ключ — отсутствовал: приходилось прибегать к менее очевидным способам, например Map.filter() или Map.filterKeys(). Теперь этот пробел заполняет оператор minus. Доступны 4 перегрузки: для удаления одного ключа, коллекции ключей, последовательности ключей и массива ключей.
fun main(args: Array<String>) {
//sampleStart
val map = mapOf("key" to 42)
val emptyMap = map - "key"
//sampleEnd
println("map: $map")
println("emptyMap: $emptyMap")
}
minOf() и maxOf()
Эти функции позволяют найти наименьшее и наибольшее из двух или трёх заданных значений, которыми могут быть примитивные числа или объекты Comparable. У каждой функции есть также перегрузка, принимающая дополнительный экземпляр Comparator, если нужно сравнить объекты, которые сами по себе несравнимы.
fun main(args: Array<String>) {
//sampleStart
val list1 = listOf("a", "b")
val list2 = listOf("x", "y", "z")
val minSize = minOf(list1.size, list2.size)
val longestList = maxOf(list1, list2, compareBy { it.size })
//sampleEnd
println("minSize = $minSize")
println("longestList = $longestList")
}
Функции создания списков, аналогичные созданию массивов
Подобно конструктору Array, теперь доступны функции, создающие экземпляры List и MutableList и инициализирующие каждый элемент вызовом лямбда-выражения:
fun main(args: Array<String>) {
//sampleStart
val squares = List(10) { index -> index * index }
val mutable = MutableList(10) { 0 }
//sampleEnd
println("squares: $squares")
println("mutable: $mutable")
}
Map.getValue()
Это расширение для Map возвращает существующее значение, соответствующее заданному ключу, или выбрасывает исключение с указанием ненайденного ключа. Если отображение создано с помощью withDefault, эта функция вернёт значение по умолчанию вместо выбрасывания исключения.
fun main(args: Array<String>) {
//sampleStart
val map = mapOf("key" to 42)
// returns non-nullable Int value 42
val value: Int = map.getValue("key")
val mapWithDefault = map.withDefault { k -> k.length }
// returns 4
val value2 = mapWithDefault.getValue("key2")
// map.getValue("anotherKey") // <- this will throw NoSuchElementException
//sampleEnd
println("value is $value")
println("value2 is $value2")
}
Абстрактные коллекции
Эти абстрактные классы можно использовать как базовые при реализации классов коллекций Kotlin. Для реализации коллекций только для чтения доступны AbstractCollection, AbstractList, AbstractSet и AbstractMap, а для изменяемых коллекций — AbstractMutableCollection, AbstractMutableList, AbstractMutableSet и AbstractMutableMap. В JVM эти абстрактные изменяемые коллекции наследуют большую часть функциональности от абстрактных коллекций JDK.
Функции для работы с массивами
В стандартную библиотеку добавлен набор функций для поэлементных операций с массивами: сравнения (contentEquals и contentDeepEquals), вычисления хеш-кода (contentHashCode и contentDeepHashCode) и преобразования в строку (contentToString и contentDeepToString). Они поддерживаются и для JVM (где выступают псевдонимами соответствующих функций из java.util.Arrays), и для JS (где реализованы в стандартной библиотеке Kotlin).
fun main(args: Array<String>) {
//sampleStart
val array = arrayOf("a", "b", "c")
println(array.toString()) // JVM implementation: type-and-hash gibberish
println(array.contentToString()) // nicely formatted as list
//sampleEnd
}
Бэкенд JVM
Поддержка байт-кода Java 8
Теперь Kotlin позволяет генерировать байт-код Java 8 (параметр командной строки -jvm-target 1.8 или соответствующие параметры Maven/Gradle). Пока это не меняет семантику байт-кода (в частности, методы по умолчанию в интерфейсах и лямбда-выражения генерируются точно так же, как в Kotlin 1.0), но в будущем планируется использовать эту возможность шире.
Поддержка стандартной библиотеки Java 8
Теперь доступны отдельные версии стандартной библиотеки с поддержкой новых API JDK, добавленных в Java 7 и 8. Если нужен доступ к новым API, используйте артефакты Maven kotlin-stdlib-jre7 и kotlin-stdlib-jre8 вместо стандартного kotlin-stdlib. Эти артефакты представляют собой небольшие расширения поверх kotlin-stdlib и добавляются в проект как транзитивная зависимость.
Имена параметров в байт-коде
Теперь Kotlin поддерживает сохранение имён параметров в байт-коде. Эту возможность можно включить с помощью параметра командной строки -java-parameters.
Встраивание констант
Теперь компилятор встраивает значения свойств const val в места их использования.
Изменяемые переменные замыкания
Поля классов-контейнеров, используемых для захвата изменяемых переменных замыкания в лямбда-выражениях, больше не являются volatile. Это изменение повышает производительность, но в некоторых редких сценариях использования может привести к новым состояниям гонки. Если это затрагивает ваш код, необходимо самостоятельно синхронизировать доступ к переменным.
Поддержка javax.script
Теперь Kotlin интегрируется с API javax.script (JSR-223). Этот API позволяет выполнять фрагменты кода во время работы программы:
val engine = ScriptEngineManager().getEngineByExtension("kts")!!
engine.eval("val x = 3")
println(engine.eval("x + 2")) // Prints out 5
Более подробный пример проекта с использованием API см. здесь.
kotlin.reflect.full
Чтобы подготовиться к поддержке Java 9, функции-расширения и свойства из библиотеки kotlin-reflect.jar были перенесены в пакет kotlin.reflect.full. Имена в старом пакете (kotlin.reflect) объявлены устаревшими и будут удалены в Kotlin 1.2. Обратите внимание: основные интерфейсы рефлексии (например, KClass) входят в стандартную библиотеку Kotlin, а не в kotlin-reflect, и перенос их не затрагивает.
Бэкенд JavaScript
Единая стандартная библиотека
Теперь из кода, компилируемого в JavaScript, доступна значительно большая часть стандартной библиотеки Kotlin. В частности, ключевые классы, такие как коллекции (ArrayList, HashMap и т. д.), исключения (IllegalArgumentException и т. д.) и некоторые другие (StringBuilder, Comparator), теперь определены в пакете kotlin. В JVM их имена являются псевдонимами соответствующих классов JDK, а в JS классы реализованы в стандартной библиотеке Kotlin.
Улучшенная генерация кода
Теперь бэкенд JavaScript генерирует код, который проще статически проверять и который лучше подходит для инструментов обработки JS-кода, таких как минификаторы, оптимизаторы, линтеры и т. д.
Модификатор external
Чтобы обращаться из Kotlin к классу, реализованному на JavaScript, с проверкой типов, можно написать объявление Kotlin с модификатором external. (В Kotlin 1.0 вместо него использовалась аннотация @native.) В отличие от целевой платформы JVM, в JS модификатор external можно использовать с классами и свойствами. Например, вот как можно объявить класс DOM Node:
external class Node {
val firstChild: Node
fun appendChild(child: Node): Node
fun removeChild(child: Node): Node
// etc
}
Улучшенная обработка импорта
Теперь можно точнее описывать объявления, которые нужно импортировать из модулей JavaScript. Если добавить аннотацию @JsModule("<module-name>") к внешнему объявлению, во время компиляции оно будет корректно импортировано в систему модулей (CommonJS или AMD). Например, в CommonJS объявление будет импортировано с помощью функции require(...). Кроме того, если объявление нужно импортировать как модуль или как глобальный объект JavaScript, можно использовать аннотацию @JsNonModule.
Например, вот как можно импортировать JQuery в модуль Kotlin:
external interface JQuery {
fun toggle(duration: Int = definedExternally): JQuery
fun click(handler: (Event) -> Unit): JQuery
}
@JsModule("jquery")
@JsNonModule
@JsName("$")
external fun jquery(selector: String): JQuery
В этом случае JQuery будет импортирован как модуль с именем jquery. В зависимости от настроенной системы модулей компилятора Kotlin его также можно использовать как объект $.
Использовать эти объявления в приложении можно следующим образом:
fun main(args: Array<String>) {
jquery(".toggle-button").click {
jquery(".toggle-panel").toggle(300)
}
}
© 2010–2026 JetBrains s.r.o. and Kotlin Programming Language contributors
Licensed under the Apache License, Version 2.0.
https://kotlinlang.org/docs/whatsnew11.html