Встроенные функции
Использование функций высшего порядка влечёт за собой определённые затраты во время выполнения: каждая функция является объектом, и она захватывает замыкание. Замыкание — это область видимости переменных, к которым можно получить доступ в теле функции. Распределения памяти (как для объектов функций, так и для классов) и виртуальные вызовы вводят накладные расходы во время выполнения.
Однако, похоже, что в многих случаях такие накладные расходы можно устранить, встроив выражения лямбда. Функции, показанные ниже, являются хорошими примерами такой ситуации. Функцию 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 лямбда-выражения, однако, могут обрабатываться любым желаемым способом, включая хранение в полях или передачу.
Нелокальные возвраты
В 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()
}
// ...
}
Реализованные параметры типа
Иногда вам нужно получить доступ к типу, переданному в качестве параметра:
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, её тело также проверяется, как если бы оно было публичным.
© 2010–2023 JetBrains s.r.o. and Kotlin Programming Language contributors
Licensed under the Apache License, Version 2.0.
https://kotlinlang.org/docs/inline-functions.html