Skip to content

Latest commit

 

History

History
220 lines (174 loc) · 13.9 KB

inline-functions.md

File metadata and controls

220 lines (174 loc) · 13.9 KB
type layout category title menuLabel url
doc
reference
Syntax
Встроенные (inline) функции
Встроенные функции

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

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

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

lock(l) { foo() }

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

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

Разве это не то, чего мы хотели изначально?

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

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

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

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

noinline

В случае, если вы хотите, чтобы только некоторые лямбды, переданные inline-функции, были встроены, вам необходимо отметить модификатором noinline те функции-параметры, которые встроены не будут:

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

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

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

Нелокальные return

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

fun foo() {
    ordinaryFunction {
        return // ERROR: can not make `foo` return here
    }
}

Но если функция, в которую передана лямбда, встроена, то return также будет встроен, поэтому так делать можно:

fun foo() {
    inlineFunction {
        return // OK: the lambda is inlined
    }
}

Такие return (находящиеся внутри лямбд, но завершающие внешнюю функцию) называются нелокальными (non-local). Мы используем такие конструкции в циклах, которые являются inline-функциями:

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

Заметьте, что некоторые inline-функции могут вызывать переданные им лямбды не напрямую в теле функции, а из иного контекста, такого как локальный объект или вложенная функция. В таких случаях, нелокальное управление потоком выполнения также запрещено в лямбдах. Чтобы указать это, параметр лямбды необходимо отметить модификатором 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
}

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

myTree.findParentOfType(MyTreeNodeType::class.java)

Что мы на самом деле хотим, так это передать этой функции тип, то есть вызвать её вот так:

myTree.findParentOfType<MyTreeNodeType>()

В таких случаях inline-функции могут принимать параметры вещественного типа (reified type parameters). Чтобы включить эту возможность, мы можем написать что-то вроде этого:

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

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