Spec-Zone.ru › Kotlin 1.8

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

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

Но, похоже, в многих случаях эти накладные расходы можно устранить, встроив выражения лямбда. Приведенные ниже функции являются хорошими примерами такой ситуации. Функцию 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 лямбда-выражения, однако, могут обрабатываться любым способом, включая хранение в полях или передачу.

Если у встраиваемой функции нет параметров встраиваемых функций и нет параметров типа, реифицированных в runtime, компилятор выдаст предупреждение, так как встраивание таких функций вряд ли будет полезным (вы можете использовать аннотацию @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

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

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

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

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

Последнее изменение: 10 января 2023 г.
Функции высшего порядка и лямбда-выражения Перегрузка операторов

© 2010–2023 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