Spec-Zone.ru › Kotlin 1.9

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

Выпущено: 15 февраля 2016 года

Содержание

  • Корутины

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

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

  • JVM-бекенд

  • JavaScript-бекенд

JavaScript

Начиная с Kotlin 1.1, целевая платформа JavaScript больше не считается экспериментальной. Поддерживаются все языковые возможности, и доступно много новых инструментов для интеграции с средой разработки front-end. Более подробный список изменений см. ниже.

Корутины (экспериментальная функция)

Ключевой новой функцией в 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 Backend

Поддержка байткода 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 в места, где они используются.

Переменные закрытия со свойствами mutable

Классы-боксы, используемые для захвата переменных закрытия с mutable свойствами в лямбдах, больше не имеют полей 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)
    }
}
Последнее изменение: 25 мая 2023 г.
Что нового в Kotlin 1.2 Выпуски Kotlin

© 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