Spec-Zone.ru › Kotlin 1.6

Встроенные функции

Использование функций высшего порядка влечёт за собой определённые затраты во время выполнения: каждая функция является объектом, и она захватывает замыкание. Замыкание — это область видимости переменных, к которым можно обратиться внутри тела функции. Выделение памяти (как для объектов функций, так и для классов) и виртуальные вызовы вносят накладные расходы во время выполнения.

Однако, похоже, что в многих случаях подобные накладные расходы можно устранить, встроив лямбда-выражения. Функции, показанные ниже, являются хорошими примерами такой ситуации. Функцию lock() можно легко встроить в места вызова. Рассмотрим следующий случай:

lock(l) { foo() }

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

l.lock()
try {
    foo()
} finally {
    l.unlock()
}

Чтобы заставить компилятор сделать это, отметьте функцию lock() модификатором inline.

inline fun <T> lock(lock: Lock, body: () -> T): T { ... }

Модификатор inline влияет как на саму функцию, так и на лямбды, передаваемые ей: все они будут встроены в место вызова.

Встраивание может привести к увеличению размера сгенерированного кода. Однако, если делать это разумно (избегая встраивания больших функций), это окупится в плане производительности, особенно в "мегаморфных" местах вызова внутри циклов.

noinline

Если вы не хотите, чтобы все лямбды, передаваемые во встроенную функцию, были встроены, отметьте некоторые из параметров вашей функции модификатором noinline.

inline fun foo(inlined: () -> Unit, noinline notInlined: () -> Unit) { ... }

Встраиваемые лямбды могут вызываться только внутри встроенных функций или передаваться в качестве встраиваемых аргументов. noinline лямбды, однако, могут быть обработаны любым способом, включая хранение в полях или передачу.

Если у встроенной функции нет параметров встраиваемых функций и нет реализованных параметров типа, компилятор выдаст предупреждение, так как встраивание таких функций вряд ли будет полезно (можно использовать аннотацию @Suppress("NOTHING_TO_INLINE"), чтобы подавить предупреждение, если вы уверены, что встраивание необходимо).

Нелокальные возвраты

В Kotlin вы можете использовать только обычный, неквалифицированный return для выхода из именованной функции или анонимной функции. Для выхода из лямбды используйте метку. Голый return запрещён внутри лямбды, так как лямбда не может заставить окружающую функцию return.

fun ordinaryFunction(block: () -> Unit) {
    println("hi!")
}
//sampleStart
fun foo() {
    ordinaryFunction {
        return // ERROR: cannot make `foo` return here
    }
}
//sampleEnd
fun main() {
    foo()
}

Но если функция, которой передаётся лямбда, встроена, возврат также может быть встроен. Поэтому это разрешено:

inline fun inlined(block: () -> Unit) {
    println("hi!")
}
//sampleStart
fun foo() {
    inlined {
        return // OK: the lambda is inlined
    }
}
//sampleEnd
fun main() {
    foo()
}

Такие возвраты (расположенные в лямбде, но выходящие из окружающей функции) называются нелокальными возвратами. Такой вид конструкции обычно встречается в циклах, которые часто содержат встроенные функции:

fun hasZeros(ints: List<Int>): Boolean {
    ints.forEach {
        if (it == 0) return true // returns from hasZeros
    }
    return false
}

Обратите внимание, что некоторые встроенные функции могут вызывать передаваемые в них лямбды не напрямую из тела функции, а из другого контекста выполнения, такого как локальный объект или вложенная функция. В таких случаях нелокальный поток управления также не разрешён в лямбдах. Чтобы указать, что параметр лямбды встроенной функции не может использовать нелокальные возвраты, отметьте параметр лямбды модификатором crossinline.

inline fun f(crossinline body: () -> Unit) {
    val f = object: Runnable {
        override fun run() = body()
    }
    // ...
}

break и continue ещё не доступны во встроенных лямбдах, но мы планируем поддерживать их тоже.

Реализованные параметры типа

Иногда вам нужно получить доступ к типу, переданному в качестве параметра:

fun <T> TreeNode.findParentOfType(clazz: Class<T>): T? {
    var p = parent
    while (p != null && !clazz.isInstance(p)) {
        p = p.parent
    }
    @Suppress("UNCHECKED_CAST")
    return p as T?
}

Здесь вы поднимаетесь по дереву и используете рефлексию для проверки, имеет ли узел определённый тип. Всё хорошо, но место вызова не очень красивое:

treeNode.findParentOfType(MyTreeNode::class.java)

Лучшим решением было бы просто передать тип в эту функцию. Вы можете вызвать её следующим образом:

treeNode.findParentOfType<MyTreeNode>()

Для этого встроенные функции поддерживают реализованные параметры типа, поэтому вы можете написать что-то вроде этого:

inline fun <reified T> TreeNode.findParentOfType(): T? {
    var p = parent
    while (p != null && p !is T) {
        p = p.parent
    }
    return p as T?
}

В коде выше тип параметра квалифицирован модификатором reified, чтобы сделать его доступным внутри функции, почти как обычный класс. Так как функция встроена, рефлексия не нужна, и обычные операторы, такие как !is и as, теперь доступны для использования. Также вы можете вызвать функцию, как показано выше: myTree.findParentOfType<MyTreeNodeType>().

Хотя рефлексия может не потребоваться во многих случаях, вы всё ещё можете использовать её с реализованным параметром типа:

inline fun <reified T> membersOf() = T::class.members

fun main(s: Array<String>) {
    println(membersOf<StringBuilder>().joinToString("\n"))
}

Обычные функции (не помеченные как встроенные) не могут иметь реализованные параметры. Тип, не имеющий представления во время выполнения (например, нереализованный параметр типа или фиктивный тип, как Nothing ) не может использоваться в качестве аргумента для реализованного параметра типа.

Встроенные свойства

Модификатор inline может использоваться для аксессоров свойств, у которых нет полей-экземпляров. Вы можете аннотировать отдельные аксессоры свойства:

val foo: Foo
    inline get() = Foo()

var bar: Bar
    get() = ...
    inline set(v) { ... }

Вы также можете аннотировать целое свойство, что помечает оба его аксессора как inline:

inline var bar: Bar
    get() = ...
    set(v) { ... }

В месте вызова встроенные аксессоры встраиваются как обычные встроенные функции.

Ограничения для встроенных функций публичного API

Когда встроенная функция является public или protected, но не является частью private или internal объявления, она считается публичным API модуля. К ней можно обращаться в других модулях, и она также встраивается в таких местах вызова.

Это создаёт определённые риски бинарной несовместимости, вызванные изменениями в модуле, который объявляет встроенную функцию, в случае, если вызывающий модуль не перекомпилируется после изменения.

Чтобы устранить риск введения такой несовместимости из-за изменения непубличного API модуля, для встроенных функций публичного API запрещено использование деклараций непубличного API, т.е. private и internal объявления и их части в их телах.

Декларация internal может быть аннотирована как @PublishedApi, что разрешает её использование во встроенных функциях публичного API. Когда встроенная функция internal помечена как @PublishedApi, её тело проверяется тоже, как если бы оно было публичным.

Последнее изменение: 07 апреля 2022
Функции высшего порядка и лямбды Перегрузка операторов

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

Spec-Zone.ru

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