Встроенные (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-функция не имеет ни встраиваемых функциональных параметров, ни параметров вещественного типа, компилятор выдаст предупреждение, так как встраивание такой функции вряд ли принесёт пользу (используйте
@Suppress("NOTHING_TO_INLINE")для скрытия предупреждения, если вы уверены, что встраивание необходимо).
Нелокальные операторы перехода
Оператор return
В Kotlin вы можете использовать обыкновенный, безусловный return только для выхода из именованной или анонимной
функции. Это значит, что для выхода из лямбды вам нужно использовать метку.
Обычный return запрещён внутри лямбды, потому что она не может заставить внешнюю функцию завершиться.
fun ordinaryFunction(block: () -> Unit) {
println("hi!")
}
//sampleStart
fun foo() {
ordinaryFunction {
return // ERROR: нельзя заставить `foo` завершиться здесь
}
}
//sampleEnd
fun main() {
foo()
}
Но если функция, в которую передана лямбда, встроена, то return также будет встроен, поэтому так делать можно:
inline fun inlined(block: () -> Unit) {
println("hi!")
}
//sampleStart
fun foo() {
inlined {
return // OK: лямбда встроена
}
}
//sampleEnd
fun main() {
foo()
}
Такие операторы return (находящиеся внутри лямбд, но завершающие внешнюю функцию) называются нелокальными возвратами
(ориг.: non-local returns). Такие конструкции обычно используются в циклах, которые часто находятся внутри
inline-функций:
fun hasZeros(ints: List<Int>): Boolean {
ints.forEach {
if (it == 0) return true // возвращает из hasZeros
}
return false
}
Заметьте, что некоторые inline-функции могут вызывать переданные им лямбды не напрямую в теле функции, а из иного
контекста, такого как локальный объект или вложенная функция. В таких случаях нелокальное управление потоком выполнения
также запрещено в лямбдах. Чтобы указать, что параметр-лямбда inline-функции не может использовать нелокальные return,
пометьте этот параметр модификатором crossinline.
inline fun f(crossinline body: () -> Unit) {
val f = object: Runnable {
override fun run() = body()
}
// ...
}
Операторы break и continue
Как и нелокальный return, вы можете использовать операторы перехода break и continue в лямбдах,
переданных как аргументы inline-функции, если вызов этой inline-функции находится внутри цикла:
fun processList(elements: List<Int>): Boolean {
for (element in elements) {
val variable = element.nullableMethod() ?: run {
log.warning("Элемент равен null или недопустим, продолжаем...")
continue
}
if (variable == 0) return true
}
return false
}
Параметры вещественного типа
Иногда вам необходимо получить доступ к типу, переданному в качестве параметра:
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 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), не может
использоваться в качестве аргумента для параметра вещественного типа.
Встроенные свойства
Модификатор inline можно применять к методам доступа свойств, у которых нет теневых полей.
Вы можете аннотировать отдельные методы доступа.
val foo: Foo
inline get() = Foo()
var bar: Bar
get() = ...
inline set(v) { ... }
Также можно аннотировать свойство. В этом случае оба его метода доступа будут отмечены как inline.
inline var bar: Bar
get() = ...
set(v) { ... }
В месте вызова встроенные методы доступа встраиваются как обычные inline-функции.
Ограничения для встроенных функций в public API
Если у встроенной функции модификатор доступа public или protected, при этом она не является частью объявления с
модификаторами доступа private или internal, то она считается public API модуля.
Её можно вызывать в других модулях, и там она также встраивается в месте вызова.
Это создаёт риск двоичной несовместимости из-за изменений в модуле, который объявляет встроенную функцию, если вызывающий модуль не был перекомпилирован после этих изменений.
Чтобы исключить риск двоичной несовместимости, вызванной изменением non-public API модуля, public API inline-функциям
не разрешается использовать в своих телах объявления non-public-API, т.е. объявления private и internal, а также их
части.
Объявление с модификатором internal может быть аннотировано при помощи @PublishedApi, что позволит его использовать
в public API inline-функциях. Когда встроенная функция с модификатором доступа internal помечена как @PublishedApi,
её тело тоже проверяется, как если бы она была public.