Spec-Zone.ru › Kotlin 1.7

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

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

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

Последнее изменение: 06 сентября 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