Spec-Zone.ru › Kotlin 1.4

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

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

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

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("привет!") }

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

Для описания на низком уровне см. документ спецификации.

Встроенные свойства (с 1.1)

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

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

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

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

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

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

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

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

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

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

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

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

Spec-Zone.ru

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