Встроенные функции
Использование функций высшего порядка влечёт за собой определённые накладные расходы во время выполнения: каждая функция является объектом, и она захватывает замыкание, т.е. те переменные, к которым обращается в теле функции. Выделение памяти (как для объектов функций, так и для классов) и виртуальные вызовы вносят накладные расходы во время выполнения.
Однако, похоже, что в многих случаях эти накладные расходы можно устранить, встроив выражения лямбда. Приведённые ниже функции являются хорошими примерами этой ситуации. То есть, функция 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 лямбда-выражения могут обрабатываться любым способом, который мы хотим: храниться в полях, передаваться и т.д.
Обратите внимание, что если встроенная функция не имеет параметров-функций для встраивания и не имеет реализованных параметров типа, компилятор выдаст предупреждение, так как встраивание таких функций вряд ли будет выгодным (вы можете подавить предупреждение, если уверены, что встраивание необходимо, используя аннотацию @Suppress("NOTHING_TO_INLINE")).
Нелокальные возвраты
В Kotlin мы можем использовать только обычный, неквалифицированный 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("привет!") }
//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()
}
// ...
}
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?
}
Здесь мы поднимаемся по дереву и используем рефлексию для проверки, имеет ли узел определённый тип. Всё хорошо, но место вызова не очень красивое:
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), не может использоваться в качестве аргумента для реализованного параметра типа.
Для описания на низком уровне см. документ спецификации.
Встроенные свойства (с 1.1)
Модификатор inline может использоваться для аксессоров свойств, у которых нет поля хранения. Вы можете аннотировать отдельные аксессоры свойств:
val foo: Foo
inline get() = Foo()
var bar: Bar
get() = ...
inline set(v) { ... }
Вы также можете аннотировать всё свойство, что помечает оба его аксессора как встроенные:
inline var bar: Bar
get() = ...
set(v) { ... }
В месте вызова встроенные аксессоры встраиваются как обычные встроенные функции.
Ограничения для встроенных функций публичного API
Когда встроенная функция является public или protected и не является частью private или internal объявления, она считается публичным API модуля. Её можно вызывать в других модулях, и она также встраивается в таких местах вызовов.
Это создаёт определённые риски бинарной несовместимости, вызванные изменениями в модуле, который объявляет встроенную функцию, в случае, если вызывающий модуль не перекомпилирован после изменения.
Чтобы устранить риск такой несовместимости, вводимой изменением в не-публичном API модуля, встроенные функции публичного API не могут использовать объявления не-публичного API, т.е. private и internal объявления и их части, в своих телах.
Объявление internal может быть аннотировано @PublishedApi, что позволяет использовать его во встроенных функциях публичного API. Когда встроенная функция internal помечена как @PublishedApi, её тело тоже проверяется, как если бы оно было публичным.
© 2010–2020 JetBrains s.r.o. and Kotlin Programming Language contributors
Licensed under the Apache License, Version 2.0.
https://kotlinlang.org/docs/reference/inline-functions.html