Spec-Zone.ru › Kotlin 1.8

Что нового в Kotlin 1.1

Дата выпуска: 15 февраля 2016 года

Оглавление

  • Корутины

  • Другие возможности языка

  • Стандартная библиотека

  • JVM-бекенд

  • JavaScript-бекенд

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
}

Прочитайте документацию по запечатанным классам или KEEP по запечатанному классу и классу данных для получения более подробной информации.

Деструктуризация в лямбда-выражениях

Теперь вы можете использовать синтаксис деструктурирующего объявления для распаковки аргументов, переданных в лямбда-выражение. Вот пример:

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 или соответствующие опции в Ant/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, и не затрагиваются этим перемещением.

END_OF_DOCUMENT_MARKER

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)
    }
}
Последнее изменение: 10 января 2023
Что нового в Kotlin 1.2 Что нового в Kotlin 1.8.0-RC2

© 2010–2023 JetBrains s.r.o. and Kotlin Programming Language contributors
Licensed under the Apache License, Version 2.0.
https://kotlinlang.org/docs/whatsnew11.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API