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, однако, можно делать всё что угодно, в том числе сохранять их в полях или передавать дальше.
Нелокальные выражения перехода
Возвраты
В 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, её тело также проверяется, как если бы оно было публичным.
© 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