Kotlin学习_内联函数(inline)


Using higher-order functions imposes certain runtime penalties: each function is an object, and it captures a closure, i.e. those variables that are accessed in the body of the function. Memory allocations (both for function objects and classes) and virtual calls introduce runtime overhead.

使用高阶函数在运行时会带来一些坏处:每个函数都是一个对象, 而且它还要捕获一个闭包,也就是说在函数体内部访问的那些外层变量的内存占用(函数对象和类都会占用内存) 以及虚方法调用都会带来运行时的消耗。

内联函数就是在程序编译时,编译器将程序中出现的内联函数的调用表达式用内联函数的函数体来直接进行替换,但是由于在编译时将函数体中的代码被替代到程序中,因此会增加目标程序代码量,进而增加空间开销,而在时间开销上不会像函数调用时那么大,可见它是以目标代码的增加为代价来换取时间的节省,因此如果一个内联函数是很大的话,会大幅增加调用它的那个函数的体积。
image.png

image.png

image.png

image.png

image.png

image.png

image.png

设置内联

使用关键字inline即可设置函数为内联函数:

1
2
3
inline fun <T> T.one (inlined: () -> Unit) {

}

禁用内联(noinline)

如果只有一些被内联,另外的不想内联,则可以使用关键字noinline可以将函数禁用内联:

1
2
3
inline fun <T> T.one (inlined: () -> Unit, noinline notInlined: () -> Unit) {

}

如果一个内联函数没有可内联的函数参数并且没有具体化类型参数,则会产生一个禁告,因为这样的内联函数没有什么用处。

warnning

具体化类型参数(Reified type parameters)

什么是具体化类型参数,使用 reified 修饰符来限定类型参数,可以在函数内部访问它了,就像是一个普通的类。
为什么要会有这样的参数,举个栗子,访问一个类型作为参数传递的时候,是这样的:

1
2
3
4
5
6
7
8
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?
}

如果要向上遍历,并且检查每隔节点是不是特定的类型,调用的时候一点都不优雅

1
treeNode.findParentOfType(MyTreeNode::class.java)

如果改成这样的话就很优雅了:

1
treeNode.findParentOfType<MyTreeNode>()

这样就是具体化类型参数所带来的一些好处,函数就要修改为

1
2
3
4
5
6
7
inline fun <reified T> TreeNode.findParentOfType(): T? {
var p = parent
while (p != null && p !is T) {
p = p.parent
}
return p as T?
}

上面提到的如果一个内联函数的参数都是noinline的话,会发出一个警告,这时候可以加个具体化参数来解除警告

1
2
3
inline fun <reified T> T.one (noinline inlined: () -> Unit, noinline notInlined: () -> Unit) {

}

具体化参数类型只能在内联函数上使用。

非局部返回(Non-local returns)

在Kotlin中,只能使用一个正常非限定的return来退出一个命名函数或者匿名函数,意味着退出Lambdas表达式不能使用单独的return,必须标签,这是由于Lambda表达式不能使包含它的函数返回

1
2
3
4
5
fun foo() {
ordinaryFunction {
return // 错误:不能使 `foo` 在此处返回
}
}

如果Lambdas表达式传给的函数是内联函数的话,return也是内联的,可以单独return

1
2
3
4
5
fun foo() {
inlineFunction {
return // OK:该 lambda 表达式是内联的
}
}

而这种返回(位于 lambda 表达式中,但退出包含它的函数)在Kotlin中称为非局部返回。
例如我们常用的foreach也是内联函数

foreach

调用

1
2
3
4
5
6
fun hasZeros(ints: List<Int>): Boolean {
ints.forEach {
if (it == 0) return true // 从 hasZeros 返回
}
return false
}

如果有内联函数的调用不是由自己函数体传递的,而是由另一个执行上下面的Lambdas表达式参数传递调用,如局部对象或者嵌套函数等,这样的Lambdas表达式是不允许局部控制操作,为了标识这种情况,加crossinline关键字

1
2
3
4
5
6
inline fun f(crossinline body: () -> Unit) {
val f = object: Runnable {
override fun run() = body()
}
// ……
}

当前breakcontinue在内联的Lambda表达式中还不支持

内联属性(自 1.1 起)

inline修饰符可用于没有备用字段的属性访问器:

1
2
3
4
5
6
val foo: Foo
inline get() = Foo()

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

可以标注整个属性:

1
2
3
inline var bar: Bar
get() = ……
set(v) { …… }

公有 API 内联函数的限制

当一个内联函数是 public 或 protected 而不是 private 或 internal 声明的一部分时,就会认为它是一个模块级的公有 API。可以在其他模块中调用它,并且也可以在调用处内联这样的调用。

这带来了一些由模块做这样变更时导致的二进制兼容的风险——声明一个内联函数但调用它的模块在它修改后并没有重新编译。

为了消除这种由非公有 API 变更引入的不兼容的风险,公有 API 内联函数体内不允许使用非公有声明,即,不允许使用 private 与 internal 声明以及其部件。

一个 internal 声明可以由 @PublishedApi 标注,这会允许它在公有 API 内联函数中使用。当一个 internal 内联函数标记有 @PublishedApi 时,也会像公有函数一样检查其函数体。