Spec-Zone.ru › Kotlin 2

Inline-функции

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

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

lock(l) { foo() }

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

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

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

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

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

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

noinline

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

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

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

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

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

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

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

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

break и continue

Подобно нелокальным return, выражения перехода break и continue можно использовать в лямбда-выражениях, переданных в качестве аргументов inline-функции, охватывающей цикл:

fun processList(elements: List<Int>): Boolean {
    for (element in elements) {
        val variable = element.nullableMethod() ?: run {
            log.warning("Element is null or invalid, continuing...")
            continue
        }
        if (variable == 0) return true
    }
    return false
}

Параметры типа с reified

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

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-функции поддерживают параметры типа с reified, поэтому можно написать что-то вроде этого:

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>().

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

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

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

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

Inline-свойства

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

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

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

Можно также аннотировать свойство целиком, пометив оба его метода доступа как inline:

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

В месте вызова встроенные методы доступа встраиваются так же, как обычные inline-функции.

Ограничения для inline-функций публичного API

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

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

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

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

23 июня 2025 г.
Параметры контекстаПерегрузка операторов

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