Что нового в 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 для получения дополнительных сведений.
Запечатанные и данные классы
Kotlin 1.1 удаляет некоторые ограничения на запечатанные и данные классы, которые были в Kotlin 1.0. Теперь можно определять подклассы верхнего уровня запечатанного класса на верхнем уровне в одном файле, а не только как вложенные классы запечатанного класса. Данные классы теперь могут расширять другие классы. Это можно использовать для определения иерархии классов выражений красивым и чистым способом:
//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
}
Прочитайте документацию по запечатанным классам или KEEPs для запечатанного класса и данного класса для получения дополнительных сведений.
Деструктуризация в лямбда-выражениях
Теперь можно использовать синтаксис деструктурирующего объявления для распаковки аргументов, передаваемых в лямбда-выражение. Вот пример:
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, если нет. В сочетании с оператором эльвис (?:) и оператором return на ранней стадии это позволяет писать конструкции, подобные:
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 опция командной строки или соответствующие опции в Ant/Maven/Gradle). Пока это не изменяет семантику байт-кода (в частности, методы по умолчанию в интерфейсах и лямбда-выражения генерируются точно так же, как в Kotlin 1.0), но мы планируем в дальнейшем использовать это.
Поддержка стандартной библиотеки Java 8
Теперь существуют отдельные версии стандартной библиотеки, поддерживающие новые API JDK, добавленные в Java 7 и 8. Если вам нужен доступ к новым API, используйте kotlin-stdlib-jre7 и kotlin-stdlib-jre8 артефакты maven вместо стандартного kotlin-stdlib. Эти артефакты — это крошечные расширения поверх kotlin-stdlib, и они добавляются в ваш проект как транзитивная зависимость.
Имена параметров в байт-коде
Kotlin теперь поддерживает хранение имен параметров в байт-коде. Это можно включить, используя опцию командной строки -java-parameters.
Константное инлайнирование
Компилятор теперь инлайнит значения const val свойств в места их использования.
Изменяемые переменные замыканий
Классы-боксы, используемые для захвата изменяемых переменных замыканий в лямбда-выражениях, больше не имеют полей volatile. Это изменение улучшает производительность, но может привести к новым проблемам гонок в некоторых редких сценариях использования. Если вы затронуты этим, вам необходимо предоставить свою синхронизацию для доступа к переменным.
Поддержка javax.script
Kotlin теперь интегрирован с javax.script API (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 бэкэнд
Единая стандартная библиотека
Значительно большая часть стандартной библиотеки Kotlin теперь может использоваться из кода, скомпилированного в JavaScript. В частности, ключевые классы, такие как коллекции (ArrayList, HashMap и т. д.), исключения (IllegalArgumentException и т. д.) и несколько других (StringBuilder, Comparator) теперь определены в пакете kotlin. В JVM эти имена являются псевдонимами типов для соответствующих классов JDK, а в JS — классами, реализованными в стандартной библиотеке Kotlin.
Улучшенная генерация кода
JavaScript бэкэнд теперь генерирует код, который легче поддается статической проверке, что делает его более дружественным для инструментов обработки JS-кода, таких как сжатие, оптимизация, проверки кода и т. д.
Модификатор external
Если вам нужно получить доступ к классу, реализованному в JavaScript, из Kotlin безопасным способом, вы можете написать объявление 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–2022 JetBrains s.r.o. and Kotlin Programming Language contributors
Licensed under the Apache License, Version 2.0.
https://kotlinlang.org/docs/whatsnew11.html